Лисковская подстанция и композиция

Скажем, у меня есть класс вроде этого:

public sealed class Foo
{
    public void Bar
    {
       // Do Bar Stuff
    }
}

И я хочу расширить его, чтобы добавить что-то помимо того, что мог бы сделать метод расширения... Моя единственная опция - состав:

public class SuperFoo
{
    private Foo _internalFoo;

    public SuperFoo()
    {
        _internalFoo = new Foo();        
    }

    public void Bar()
    {
        _internalFoo.Bar();
    }

    public void Baz()
    {
        // Do Baz Stuff
    }
}

Пока это работает, это большая работа... однако у меня все еще возникает проблема:

  public void AcceptsAFoo(Foo a)

Я могу передать в Foo здесь, но не супер Foo, потому что С# не знает, что SuperFoo действительно делает квалификацию в смысле Liskov Substitution... Это означает, что мой расширенный класс через композицию очень ограничен.

Таким образом, единственный способ исправить это - надеяться, что первоначальные дизайнеры API оставят интерфейс, лежащий вокруг:

public interface IFoo
{
     public Bar();
}

public sealed class Foo : IFoo
{
     // etc
}

Теперь я могу реализовать IFoo на SuperFoo (который, поскольку SuperFoo уже реализует Foo, это просто вопрос изменения подписи).

public class SuperFoo : IFoo

И в совершенном мире методы, которые потребляют Foo, будут потреблять IFoo:

public void AcceptsAFoo(IFoo a)

Теперь С# понимает взаимосвязь между SuperFoo и Foo из-за общего интерфейса, и все хорошо.

Большая проблема заключается в том, что .NET печатает множество классов, которые иногда могут быть полезными для расширения, и обычно они не реализуют общий интерфейс, поэтому методы API, которые принимают Foo, не будут принимать SuperFoo, и вы не может добавить перегрузку.

Итак, для всех поклонников композиции там... Как вы обойдете это ограничение?

Единственное, что я могу придумать, - публиковать публичный Foo публично, чтобы вы могли его иногда передавать, но это кажется грязным.

Ответы

Ответ 1

Я боюсь, что короткий ответ: вы не можете не выполнять то, что требуется, то есть передать вместо этого скомпилированную переменную экземпляра.

Вы могли разрешать неявное или явное приведение к этому типу (реализация которого просто передала скомпонованный экземпляр), но это будет, IMO будет довольно злым.

sixlettervariable ответ хорош, и я не буду его перефразировать, но если вы указали, какие классы вы хотели бы расширить, мы могли бы рассказать вам, почему они это предотвратили.

Ответ 2

Я выяснил, что задаю этот вопрос до тех пор, пока не приступим к работе над многократно используемыми библиотеками. Много раз вы завершаетесь определенными классами, которые просто не могут быть расширены, не требуя скрытых или тайных последовательностей вызовов от исполнителя.

При разрешении расширения вашего класса вы должны спросить: может ли разработчик продлить мой класс и передать этот новый класс в мою библиотеку, могу ли я прозрачно работать с этим новым классом? Могу ли я правильно работать с этим новым классом? Этот новый класс действительно будет вести себя одинаково?

Я обнаружил, что большую часть времени запечатанные классы в .Net Framework имеют определенные требования, которые вы не знаете, и которые, учитывая текущую реализацию, не могут быть безопасно подвержены подклассам.

Это точно не отвечает на ваш вопрос, но дает представление о том, почему не все классы наследуются в .NET Framework (и почему вы, вероятно, должны развлекать некоторые из своих классов тоже).