这违反了我的SOLID原则吗?

8

我正在尝试使用SOLID原则学习更好的编程实践。这里我正在开发一个形状示例应用程序。我只是想知道,我是否在任何地方违反了该原则。以下是类及其代码。

1. 基类 - Shape

public abstract class Shape
{
    public abstract double Area();
    public virtual double Volume() 
    { 
        throw new NotImplementedException("You cannot determine volume from here...Method not implemented."); 
    }
}

2. 形状类,如矩形、三角形等,实现了基类Shape。
public class Circle : Shape
{
    public int Radius { get; set; }
    public override double Area() { return 3.14 * Radius * Radius; }
}

public class Triangle : Shape
{
    public int Height { get; set; }
    public int Base { get; set; }
    public override double Area()
    {
        return 0.5 * Base * Height;
    }
}

public class Rectangle : Shape
{
   public int Length { get; set; }
   public int Breadth { get; set; }
   public override double Area()
   {
        return Length * Breadth;
   }
}

public class Square : Shape
{
    public Square() { }
    public int Side { get; set; }
    public override double Area()
    {
        return Side * Side;
    }
}

一个返回形状的工厂类。
internal class ShapeFactory<K, T> where T : class, K, new()
{
    static K k;
    private ShapeFactory() { }

    public static K Create()
    {
        k = new T();
        return k;
    }
}

到这里,一切都看起来很好,但当我实施它时出现了问题。我有点困惑。让我们先看前端代码:
internal class Program
{
    private static void Main(string[] args)
    {
        try
        {

            var c = ShapeFactory<Shape, Circle>.Create();
            // this part is not clear to me. See the questions below
            if(c is Circle)
            {
                var circle = c as Circle;
                circle.Radius = 5;
                Console.WriteLine(string.Format("{0}", circle.Area()));
            }


        }

        catch (Exception ex)
        {

            Console.WriteLine("Error: {0}", ex.Message);
        }
        Console.Read();
    }
}

问题:
  1. 不同的形状具有不同的属性,例如圆形具有半径,三角形具有底和高等等,所以我决定将我的属性保留在子类中。我知道,我可以在基类中将其作为虚成员。那么除了上面编码之外,还有其他方法吗?

  2. 如果没有,那么抽象类有什么用处,如果我仍然将我的Shape对象强制转换为circle对象?我可以简单地使用Circle c = new Circle()。我不想要不需要的检查,如(if c is circle)等。

  3. 如果我被要求实现一个新方法来获取圆的周长,该怎么办?我需要创建一个新的抽象类还是将其放入Circle类中?但是,如果我将它放在Circle中,我认为它会违反SOLID的第一原则,即SRP。请注意,我不希望我的抽象类成为具有不必要或重复属性的大类。

提前致谢


5
http://codereview.stackexchange.com/ 更适合这个问题。 - Papa
4个回答

6

在这种情况下,我通常会在具体类中传递构造函数参数。因此,我会将您的具体形状改为:

public class Circle : Shape
{
    public int Radius { get; set; }

    public Circle(int radius) {
        this.Radius = radius;
    }

    public override double Area() { return 3.14 * this.Radius * this.Radius; }
}

public class Rectangle : Shape
{
   public int Length { get; set; }
   public int Breadth { get; set; }

   public Rectangle(int lenght, int breadth) {
        this.Length = lenght;
        this.Breadth = breadth;
   }

   public override double Area()
   {
        return Length * Breadth;
   }
}

现在,我将使用工厂模式来改进您的代码,因此您的代码将变成这样:

现在,我会使用工厂方法,所以您的工厂现在会是这样:

public abstract class ShapeFactory
{
    abstract Create();
}

public class CircleFactory : ShapeFactory
{
    private int radius;

    public CircleFactory(int radius){
        this.radius = radius;
    }

    protected override Shape Create()
    {
        return new Circle(this.radius);
    }
}

public class RectangleFactory : ShapeFactory
{
    private int length;
    private int breadth;

    public RectangleFactory(int length, int breadth){
        this.lenght = length;
        this.breadth = breadth;     
}

    protected override Shape Create()
    {
        return new Rectangle(this.length, this.breadth);
    }
}

请注意,现在一个工厂知道如何在其自己的构造函数中传递构造函数来构建形状。
因此,每次您想要不同的形状时,都会实例化一个新的工厂。
ShapeFactory factory = new CircleFactory(5);
Shape shape = factory.Create();
Console.WriteLine(shape.Area()));

我认为这回答了你的第一和第二个问题。
所以,第三个问题: 如果不修改你的类,你可以使用策略模式,在运行时传递如何实现该方法。
public interface IPerimeter
{
    int calculatePerimeter();
}

public class Circunference : IPerimeter 
{
    public int calculatePerimeter(Circle circle) {
        return 2*pi*circle.radius;
    } 
}

public class Circle : Shape
{
    public int Radius { get; set; }
    private IPerimeter perimeter;

    public Circle(int radius, IPerimeter perimeter) {
        this.Radius = radius;
        this.perimeter = perimeter;
    }

    public Circunference() {
        perimeter.calculatePerimeter(this);
    }

    public override double Area() { return 3.14 * this.Radius * this.Radius; }
}

希望这有助于您的培训。

感谢您的回答,真的非常感激。还有一个问题,ShapeFactory类是用来做什么的?您在回答中没有提到它。您是在使用我的通用类,还是其他自己编写的类?我能看到CircleFactory和RectangleFactory这两个具体类。 - user240141
哦,我忘记了这个类... 这个类是一个抽象类,有一个简单的抽象方法Create().. 已在上面更新。 - guijob
我认为你的代码中有几个错误。例如,我认为你想要 public int Circunference()(否则它看起来像一个构造函数),并且我猜你想要它返回该计算结果。此外,IPerimeter 的方法应该是 int calculatePerimeter(Circle circle);。最后,这个方法应该返回 (int)(2 * Math.PI * circle.Radius)。顺便说一下,还有一些拼写错误,应该是 length。 ;) - Andrew

0
对我来说,你的示例看起来过于复杂了。我认为你应该始终实现最简单的能够工作的东西,不多不少。我知道这是一个示例代码,因为你想学习SOLID原则,但我认为重要的是意识到这些原则在错误的上下文中可能走得有多么糟糕。在你的具体代码中:你需要使用Shape类来分组所有的形状吗?我的意思是,你是否计划遍历一个形状列表并计算它们的面积和体积?如果不是的话,继承就完全没有意义。事实上,我要说继承在当今被过度使用了,而且当它被过度使用时,你最后会得到丑陋的继承依赖图。关于工厂类:你的任何“形状”对象的构造是否特别困难、耗时或棘手?你的工厂类提供了一些价值还是完全没有用处?如果没有真正存在的理由,我不会使用它,直接使用new操作符更清晰明了。
我希望你不介意我的回复,但我只是想让你知道一些SOLID原则只适用于非常特定的场景。在错误的地方强制使用它们可能会导致丑陋和过于复杂的代码。在某些实际情况下,如果上述问题的答案是肯定的,那么你的模式似乎是可以的。否则,完全相同的模式可能会使事情变得过于复杂,而没有任何真正的好处。我的观点是:要有意识,不是每个SOLID原则在任何情况下都是好的 :)。

0
  1. 不同的子类将拥有不同的属性,这是预期的并且可以接受的。通常,并非所有派生类都具有与其基类完全相同的属性。没有理由强制 Shape 具有 Radius。你会得到什么好处?那只会为麻烦开门。你的最终目标是什么?像 myShape.Dimension = value 这样的东西,而不关心它是半径、边长等等?根据你的需求,任何事情都可以做到。

  2. 通过抽象类,例如,您可以循环遍历 Shape 列表并调用 Area()Volume(),知道您将获得结果(尽管您还没有实现 Volume)。此外,您的基类可能具有一些公共代码,在这种情况下,您没有使用。例如,您可以拥有一个 Unit 属性,它可以是 cm、inches、meters 等,然后有一个方法像这样(愚蠢的例子):

    public string GetAreaString()
    {
        return string.Format("{0} {1}", this.Area().ToString(), this.Unit);
    }
    
  3. 当然要在 Circle 中实现它。为什么会破坏 Circle 的单一职责?你的类正在处理其相关值的计算,就像一个 string 告诉你它是否为空或其长度。


0
这是一个非常普遍的问题。虽然学习SOLID很好,但它需要理解基本的设计原则,如抽象和间接性。你感到困惑的原因是因为你的代码中没有抽象概念。
想象一下,你有一段代码想要知道形状的面积,但它不关心形状是什么,也不知道如何计算该形状的面积。就像这样:
public void PrintArea(Shape shape)
{
    Console.WriteLine(shape.Area());
}

这是面向对象编程设计的关键部分。你的例子完全没有这方面的内容。你的例子只是一个杜撰的代码片段,没有逻辑可言,更不用说符合SOLID原则了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接