Лисковская субстанция и состав - PullRequest
9 голосов
/ 16 февраля 2009

Допустим, у меня есть такой класс:

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, потому что C # не имеет представления, что SuperFoo действительно подходит в смысле подстановки Лискова ... Это означает, что мой расширенный класс с помощью композиции имеет очень ограниченное использование.

Итак, единственный способ исправить это - надеяться, что разработчики оригинального 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)

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

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

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

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

Ответы [ 2 ]

14 голосов
/ 16 февраля 2009

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

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

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

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

3 голосов
/ 16 февраля 2009

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

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

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

...