Нужна помощь с дизайном .Net SOLID

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

По сути, мне нужна иерархия «узловых» объектов. Некоторые узлы являются NodeHosts, некоторые - NodeChildren, а некоторые - обоими. Все делали это раньше, но я не могу понять, как сделать это SOLID, не усложняя дизайн или не делая что-то вроде этого в подтипах узлов:

 INodeHostType node;
 public INodeType NodeType
 {
 ....
      set 
      {
          node = (INodeHostType)value;
      }
 }

Это нарушает принцип замены Лискова, верно? Что лучше? Вот что у меня есть сейчас. alt text


person Dave Welling    schedule 17.08.2010    source источник
comment
Вы имеете в виду Твердые принципы Боба Мартина. Мартин Фаулер любит писать о шаблонах проектирования.   -  person Oded    schedule 17.08.2010
comment
Нет, Мартин Фаулер - тот сумасшедший парень из EastEnders   -  person fletcher    schedule 17.08.2010
comment
Ах да ... Я не знаю, почему я всегда путаю этих двоих. Я решил вопрос с правильным парнем.   -  person Dave Welling    schedule 18.08.2010
comment
Почему бы вам не использовать составной шаблон дизайна? Я не думаю, что это нарушает твердые принципы.   -  person Arseny    schedule 02.03.2011


Ответы (1)


Мужик этот ответ действительно вырос, но было весело. Начнем :)

Согласен, это довольно сложный дизайн. Интерфейсы полезны, когда вы используете их для «абстрагирования того, что меняется», но в этом примере абстрагировано все. Этому примеру было действительно трудно следовать, так что это должно быть большим признаком того, что что-то не так. Противоположностью коду спагетти является код лазаньи (для многих слоев), и это именно то, что представляет собой эта диаграмма.

Как я вижу, у вас есть 3 группы.

  • Узлы (дочерние, родительские и т. Д.)
  • Тип узла
  • Оказывать

Начнем с узлов. Это правда, что SOLID сообщает вам код для интерфейса, но это не обязательно означает, что это должен быть реальный интерфейс. Совершенно приемлемо использовать обычное старое наследование с дочерним узлом, расширяющим базовый узел. Это по-прежнему Solid и имеет смысл в данном случае.

public class Node
{

    public var NodeType { get; set; }
    public var DisplayText { get; set; }
    public IRenderable Renderer { get; set; }

    public Node()
    {
        // TODO: Add constructor logic here
    }

    public void Render()
    {
        Renderer.Render();
    }
}

public class ChildNode : Node
{
    public var Owner {get; set;}
    public var Sequence {get; set;}

    public ChildNode()
    {
        NodeType = "Child"; //use an enum
        DisplayText = "nom nom nom babies";
        Renderer = new ChildRenderer();
    }
}

//Parent Node is more of the same

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

//This didn't add value so its just an enum used in the baseclass now

Что касается рендеринга, теперь вы кое-что знаете. Поскольку ChildNodes и ParentNodes отображаются по-разному, имеет смысл абстрагироваться от них. Хотя я вижу, что все свойства в IRenderer дублируются в IRenderContext, поэтому я просто собираюсь свернуть их в 1.

interface IRenderable
{
    //properties
    // TODO: Add properties here

    //methods
    void Render();
}

interface ChildRenderer : IRenderable
{
    void Render()
    {
        //Render Me Here
    }
}

//ParentRender() is more of the same

//I could add all sorts of functionallity with out touching other code
//     ManChildRenderer(), TripletsChildRenderer()

И диаграмма классов будет выглядеть примерно так. Хороший пример

Ладно, все хорошо, но зачем делать всю дополнительную работу, которая требуется? Посмотрим на окончательную реализацию.

public static void main()
{
    //if this isnt acceptle instantiation use a NodeFactory
    Node node1 = new ParentNode();
    Node node2 = new ChildNode();

    //Now we don't care about type -- Liskov Substitution Principle
    node1.Render();
    node2.Render();

    //adding or changing functionality is now non-breaking
    node1.Renderer = new ManChildRender();
    //I've added a whole new way to render confident I didnt break the old renders
    //In fact I didn't even upon those class files
}
person Evan    schedule 18.03.2011
comment
+1 TL; DR, но +1 за усилие. Надеюсь, другие сочтут нужным похвалить эти усилия и / или предложить критику, если они заслуживают ... - person Ruben Bartelink; 19.03.2011
comment
Если вы пропустите примеры кода, это неплохо. Я думал, что они важны для понимания спрашивающего :) - person Evan; 19.03.2011
comment
+1 Действительно большие усилия для этого. Все еще обдумывает последствия. Я пошел другим путем, не получив известий ни от кого, так что, возможно, мы сможем сравнить, как только я все обдумаю. - person Dave Welling; 21.03.2011