Нужна помощь с .Net SOLID design - PullRequest
2 голосов
/ 17 августа 2010

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

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

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

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

1 Ответ

4 голосов
/ 19 марта 2011

Человек, этот ответ действительно вырос, но это было весело.Вот и мы:)

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

Из того, что я вижу, у вас есть 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()

И диаграмма классов будет выглядеть примерно такэтот.Solid Example

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

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
}
...