Делить одну иерархию классов на несколько или нет? - PullRequest
1 голос
/ 19 февраля 2020

На данный момент у нас есть приложение. NET, которое опирается на стандарт. NET XML Механизм сериализации / десериализации. Пример упрощен, но смысл тот же.

public abstract class Shape
{
    [XmlAttribute("id")]
    public string Id { get; set; }
    [XmlAttribute("level")]
    public int Level { get; set; }

    public abstract void Draw();
    public abstract void Clear();
    public abstract void Scale(double scale);
}

[XmlType("Circle")]
public class Circle : Shape
{
    public double Radius { get; set; }

    public override void Draw() {}

    public override void Clear() {}

    public override void Scale(double scale) {}
}

[XmlType("Rectangle")]
public class Rectangle: Shape
{
    public double Height { get; set; }
    public double Width { get; set; }

    public override void Draw() {}

    public override void Clear() {}

    public override void Scale(double scale) {}
}

public class Picture
{
    public double Scale { get; set; }
    [XmlArrayAttribute("Shapes")]
    public Collection<Shape> Shapes { get; set; }

    public void Setup()
    {
        foreach (Shape shape in Shapes)
        {
            shape.Draw();
        }

        foreach (Shape shape in Shapes)
        {
            shape.Scale(Scale);
        }
    }

    public void Cleanup()
    {
        foreach (Shape shape in Shapes)
        {
            shape.Clear();
        }
    }

    public static Picture FromXml(XmlReader xmlReader)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(Picture));
        return serializer.Deserialize(xmlReader) as Picture;
    }
}

И, например, входной файл XML будет выглядеть так:

<Picture>
    <Scale>0.9</Scale>
    <Shapes>
        <Circle id="1">
            <Radius>1.5</Radius>
        </Circle>
        <Circle id="2">
            <Radius>3</Radius>
        </Circle>
        <Rectangle id="3">
            <Height>300</Height>
            <Width>300</Width>
        </Rectangle>
    </Shapes>
</Picture>

Но классы моделей содержат лог c (Методы Draw (), Clear () и Scale ()), и кажется, что это нарушает принцип единой ответственности. И поэтому мы не знаем, имеет ли смысл разделять эти логики c на несколько классов или нет?

Если да, то как? Поскольку после прочтения файла XML все объекты доступны только как объекты Shape, и поэтому нам придется явно приводить объект либо перед передачей if в класс обработчика, либо внутри этого метода, например:

public abstract class Drawer
{
    public abstract void Draw(Shape shape);
}

public class CircleDrawer : Drawer
{
    public override void Draw(Shape shape)
    {
        Circle circle = shape as Circle;
        if (circle == null)
        {
            throw new ArgumentException("Passed object is not of type Circle");
        }
    }
}

Если эта проблема известна, просто перенаправьте меня на этот ресурс.

Заранее спасибо.

Ответы [ 3 ]

0 голосов
/ 19 февраля 2020

Пытаясь разделить модель и бизнес-логи c Я получаю следующий код. Это приемлемо? Что касается меня, это выглядит не очень гибко: мы не должны забывать расширять оператор if, если добавляем новый ящик формы.

public abstract class Drawer
{
    public abstract void Draw();
}

public class CircleDrawer : Drawer
{
    private readonly Circle _circle;

    public CircleDrawer(Circle circle)
    {
        _circle = circle;
    }

    public override void Draw() { }
}

public class RectangleDrawer : Drawer
{
    private readonly Rectangle _rectangle;

    public RectangleDrawer(Rectangle rectangle)
    {
        _rectangle = rectangle;
    }

    public override void Draw() { }
}

public class Picture
{
    public double Scale { get; set; }
    [XmlArrayAttribute("Shapes")]
    public Collection<Shape> Shapes { get; set; }

    public void Setup()
    {
        List<Drawer> drawers = new List<Drawer>();
        foreach (Shape shape in Shapes)
        {
            if (shape is Circle)
            {
                drawers.Add(new CircleDrawer(shape as Circle));
            }
            else if (shape is Rectangle)
            {
                drawers.Add(new RectangleDrawer(shape as Rectangle));
            }
            else
            {
            }
        }

        foreach (Drawer drawer in drawers)
        {
            drawer.Draw();
        }
    }

    public static Picture FromXml(XmlReader xmlReader)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(Picture));
        return serializer.Deserialize(xmlReader) as Picture;
    }
}
0 голосов
/ 19 февраля 2020

В этом случае возникает вопрос: нужно ли, чтобы вызывающая сторона знала конкретный тип или все отображается через общий базовый класс?

В вашем примере все реализации имеют общий абстрактный базовый класс. Если все пользователи могут успешно работать с этим обычным поведением, вы просто проверяете все экземпляры, приведенные к базовому типу. Тем не менее, как вы уже упоминали, в каждом конкретном классе все больше и больше бизнес-логик c. В зависимости от того, насколько (или лучше меньше) это, абсолютно нормально, чтобы позволить это там (это, кажется, ваше текущее решение).

Если это становится все более или более сложным, это был бы хороший способ переместить это в свой собственный класс и / или методы. В этом случае вам нужен какой-то диспетчер, который решает, который заботится о данном объекте. Я начинаю использовать словарь для этих случаев, который работает как диспетчер и имеет данный тип в качестве ключа, а Action или Fun c в качестве значения, подобного этому:

var dispatcher = new Dictionary<Type, Action<Shape>>
{
    { typeof(Rectangle), DoSomethingWithRectangle },
    { typeof(Circle), DoSomethingWithCircle }
}

private void DoSomethingWithRectangle(Shape shape)
{
    var rectangle = (Rectangle)shape;
    Console.WriteLine($"Rectangle Height: {rectangle.Height} Width: {rectangle.Width}");
}

private void DoSomethingWithCircle(Shape shape)
{
    var circle = (Circle)shape;
    Console.WriteLine($"Circle Radius: {circle.Radius}");
}

Это просто тривиальный пример. Словарь должен находиться внутри диспетчера, где у вас есть несколько методов для регистрации различных типов, и вместо методов вы также можете регистрировать типы или экземпляры классов, которые могут обрабатывать каждый указанный тип экземпляра c. Если этот вид диспетчеризации все еще не соответствует всем вашим случаям, вы можете взглянуть на MediatR , который формализовал эти вещи намного больше с помощью набора типов-безопасных интерфейсов, которые вы используете для определения типов ввода, желаемое возвращаемое значение и обработчик, способный выполнять эту работу.

0 голосов
/ 19 февраля 2020

Вы должны разделить модель и бизнес-логику c, поэтому объект, который будет десериализован, будет содержать только свойства. Затем в фабрике или методе, который создает бизнес-логики c, вставьте его в качестве члена бизнес-логики круга (например) c.

...