Сокращение шаблонов в классе, который реализует интерфейсы посредством композиции

11

У меня есть класс: Aэто состав из нескольких меньших классов B, Cи D.

B, CИ Dреализовывать интерфейсы IB, ICи IDсоответственно.

Так как Aподдерживает все функциональные возможности B, Cи D, Aреализует IB, ICи IDтакже, но это, к сожалению, приводит к большой перенаправления в реализацииA

Вот так:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

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

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

Есть ли способ избавиться от какого-либо перенаправления в этом шаблоне A? B, CИ Dвсе реализовать разные , но общие функциональные возможности , и мне нравится , что Aорудия IB, ICи IDпотому , что это означает , что я могу передать в Aсебя как зависимость сопоставления этих интерфейсов, и не должен подвергать внутренние вспомогательные методы.

Ник Уделл
источник
Не могли бы вы немного рассказать о том, зачем вам нужен класс A для реализации IB, IC, ID? Почему бы просто не выставить BCD как публичную?
Эсбен Сков Педерсен
1
Я думаю , что моя основная причина для предпочитая Aреализовать IB, ICи IDон чувствует себя более разумно писать Person.Walk()в противоположность Person.WalkHelper.Walk(), например.
Ник Уделл

Ответы:

7

То, что вы ищете, обычно называют миксином . К сожалению, C # изначально не поддерживает их.

Есть несколько обходных путей: один , два , три и многие другие.

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

[pMixins] - это плагин Visual Studio, который сканирует решение для частичных классов, украшенных атрибутами pMixin. Отметив частичку вашего класса, [pMixins] может создать файл с выделенным кодом и добавить дополнительные члены в ваш класс.

Euphoric
источник
2

Хотя Visual Studio 2015 и не сокращает шаблон в самом коде, теперь в нем есть опция рефакторинга, которая автоматически генерирует шаблон для вас.

Чтобы использовать это, сначала создайте свой интерфейс IExampleи реализацию Example. Затем создайте свой новый класс Compositeи сделайте его наследуемым IExample, но не реализуйте интерфейс.

Добавьте свойство или поле типа Exampleк своему Compositeклассу, откройте меню быстрых действий для токена IExampleв Compositeфайле класса и выберите «Реализовать интерфейс через« Пример »», где «Пример» в этом случае является именем поля или свойства.

Следует отметить, что хотя Visual Studio будет генерировать и перенаправлять все методы и свойства, определенные в интерфейсе, для этого вспомогательного класса, она не будет перенаправлять события с момента публикации этого ответа.

Ник Уделл
источник
1

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

более разумно писать Person.Walk (), а не Person.WalkHelper.Walk ()

Я не согласен. Ходьба, вероятно, включает много правил и не принадлежит Personклассу - он принадлежит WalkingServiceклассу с walk(IWalkable)методом, который реализует Person IWalkable.

Всегда имейте в виду принципы SOLID при написании кода. Двумя, которые наиболее применимы здесь, являются разделение проблем (извлечение кода ходьбы в отдельный класс) и разделение интерфейса (разделение интерфейсов на конкретные задачи / функции / возможности). Звучит так, как будто у вас могут быть некоторые из I с несколькими интерфейсами, но затем вы отменяете всю свою хорошую работу, заставляя один класс реализовать их.

Стюарт Лейланд-Коул
источник
В моем примере функциональность будет реализована в отдельных классах, и у меня есть класс , состоящий из нескольких из них , как способ группировки требуемого поведения. В моем большем классе не существует никакой реальной реализации, все это обрабатывается меньшими компонентами. Возможно, вы правы, и сделать эти вспомогательные классы общедоступными, чтобы с ними можно было напрямую взаимодействовать, - это путь.
Ник Уделл
4
Что касается идеи walk(IWalkable)метода, мне лично это не нравится, потому что тогда услуги пешехода становятся ответственностью звонящего, а не человека, и последовательность не гарантируется. Любой вызывающий абонент должен знать, какую службу использовать, чтобы гарантировать согласованность, в то время как сохранение ее в классе Person означает, что ее необходимо изменить вручную, чтобы отличалось поведение при ходьбе, но при этом допускается инверсия зависимостей.
Ник Уделл
0

То, что вы ищете, это множественное наследование. Однако ни в C #, ни в Java его нет.

Вы можете расширить B из A. Это устранит необходимость в закрытом поле для B, а также в клее для перенаправления. Но вы можете сделать это только для одного из B, C или D.

Теперь, если вы можете сделать так, чтобы C наследовал от B, а D от C, тогда вам нужно иметь только A extends D ...;) - просто чтобы прояснить это, на самом деле я не поощряю это в этом примере, так как нет никаких указаний для этого.

В C ++ вы можете иметь наследование A от B, C и D.

Но множественное наследование делает программы труднее рассуждать и имеет свои проблемы; Кроме того, часто есть чистый способ избежать этого. Тем не менее, иногда без этого нужно найти компромисс между проектированием повторяющегося шаблона и тем, как раскрывается объектная модель.

Такое ощущение, что вы пытаетесь смешать композицию и наследование. Если бы это был C ++, вы могли бы использовать чистое решение наследования. Но так как это не так, я бы рекомендовал принять композицию.

Эрик Эйдт
источник
2
Вы можете иметь множественное наследование, используя интерфейсы как в java, так и в c #, как это делала операция в своем примере кода.
Роберт Харви
1
Некоторые могут рассмотреть реализацию интерфейса так же, как множественное наследование (как, скажем, в C ++). Другие могут не согласиться, потому что интерфейсы не могут представлять наследуемые методы экземпляра, что вы бы имели, если бы у вас было множественное наследование. В C # вы не можете расширять несколько базовых классов, поэтому вы не можете наследовать реализации метода экземпляра от нескольких классов, поэтому вам нужны все
шаблоны
Я согласен, что множественное наследование - это плохо, но альтернатива одиночного наследования в C # / Java с многоинтерфейсной реализацией - не панацея (без переадресации интерфейса это эргономический кошмар). После того , как в настоящее время провел некоторое время с Golang, я думаю , Go получил правильное представление о том, не имея никакого удела и вместо того, чтобы полагаться исключительно на композиции с интерфейсом переадресацией - но их конкретная реализация имеет проблемы тоже. Я думаю, что лучшее решение (которое, кажется, еще никто не реализовал) - это компоновка с пересылкой, с возможностью использования любого типа в качестве интерфейса.
Дай