【问题标题】:Abstraction and Proper Class Design抽象和适当的类设计
【发布时间】:2014-01-22 19:06:15
【问题描述】:

我对编程并不陌生,但对 OOP 相对较新,所以我有一个设计问题。实际上,我有两个问题包含在同一个问题中。

为了简单起见,假设我正在设计一个FruitBasket 类。除了FruitBasket 类,我还将设计一个Apple 类、一个Orange 类和一个Pear 类。每当我实例化一个FruitBasket 类型的对象时,它都会自动实例化这些其他类的has-a 关系。

class Apple { //Apple implementation here }

class Orange { //Orange implementation here }

class Pear { //Pear implementation here }

class FruitBasket
{
     Apple _apple;
     Orange _orange;
     Pear _pear;

     public FruitBasket()
     {
          _apple = new Apple();
          _orange = new Orange();
          _pear = new Pear();
     }
}

class Program
{
     FruitBasket _fruitBasket;

     static void Main()
     {
          _fruitBasket = new FruitBasket();
     }
}

好的,下面是我的问题:

  1. FruitBasket 构造函数中实例化各种水果对象是否可以接受,如图所示,还是这种做法不好?
  2. 如果每个水果篮都包含所有三种水果,这种方法似乎很好,但是,如果我希望实例化只有AppleOrange 的水果篮怎么办? PearOrange? ApplePear?等等。最好的方法是什么?

创建一个FruitBasket 类来声明可能进入篮子的每个水果并传递一些参数告诉FruitBasket 类要实例化哪些水果类会更好吗?

我真的不知道什么是最有效的,或者我的想法是否在球场上。

【问题讨论】:

  • 这可能是Software Engineering的一个更好的问题
  • 如果我们有新的水果类芒果会有什么影响,您需要在哪里进行更改?
  • 如果您希望您的代码是可测试的,您应该通过构造函数参数传递您的类的实例(或通过属性链接它们),而不是直接在其中创建具体类。就像现在一样,它们不可模拟。

标签: c# class oop constructor


【解决方案1】:

我的建议是不要为FruitBasket 创建默认构造函数。这样你就可以使用 Object Initializer 语法来做你正在谈论的事情。您需要像这样更改您的 FruitBasket 类:

public class FruitBasket
{
    public Apple apple { get; set; }
    public Orange orange { get; set; }
    public Pear pear { get; set; }
}

然后您就可以按如下方式创建 FruitBasket:

var fb1 = new FruitBasket { apple = new Apple() };
var fb2 = new FruitBasket { pear = new Pear() };
var fb3 = new FruitBasket { apple = new Apple() orange = new Orange() };

Object Initializer 语法非常强大,如果您的类真的只是一个“属性包”,它通常是最简单的方法。

编辑:您可能还希望水果从Fruit 基类继承。如果你这样做了,你可以使用对象初始化语法和集合初始化语法的组合。如果您将 FruitBasket 类更改为以下内容:

public class FruitBasket
{
    public List<Fruit> fruit { get; set; }
}

您可以按如下方式初始化您的FruitBasket

var fb3 = new FruitBasket{ fruit = new List<Fruit> { new Apple(), new Orange() } };
var fb4 = new FruitBasket{ fruit = new List<Fruit> { new Orange() } };
var fb5 = new FruitBasket{ fruit = new List<Fruit> { new Apple(), new Orange(), new Pear() } };

【讨论】:

  • 我真的很喜欢@MailmanOdd 的建议。我还将包括一个以 List 作为参数的构造函数。特别是如果你正在设计一个库,这很有意义。
  • 好主意@Chetan,我会把它添加到我的例子中。
  • 再三考虑,我建议将 IEnumerable 作为 List 或 IList 的参数。它使使用其他类型(如数组等)更加灵活。
【解决方案2】:

水果篮必须有那些水果,否则有时它可能是空的?你可以重复任何水果?如果是,你应该学习继承和泛型,你必须有一个基类 Fruit 而篮子可能只是一个泛型 List

abstract class Fruit
{

}
class Apple : Fruit
{
    //Apple implementation here
}

class Orange : Fruit
{
    //Orange implementation here
}

class Pear : Fruit
{
    //Pear implementation here
}

class Program
{

    static void Main()
    {
        List<Fruit> _fruitBasket = new List<Fruit>();

        _fruitBasket.Add(new Orange());
        _fruitBasket.Add(new Apple());
    }
}

【讨论】:

    【解决方案3】:

    或者您可以执行以下操作,让您的购物篮随心所欲:

    abstract class Fruit {}
    class Apple :Fruit  { //Apple implementation here }
    
    class Orange : Fruit  { //Orange implementation here }
    
    class Pear :Fruit  { //Pear implementation here }
    
    class FruitBasket
    {
         public List<Fruit> Contents {get;set;}
    
         public FruitBasket()
         {
           Contents = new List<Fruit>{new Apple(), new Orange(), new Pear()};
         }
    }
    

    【讨论】:

    • 我真的不喜欢这个public List&lt;Fruit&gt;,OP 要求适当的类设计,你扔掉他所有的封装(这非常好)。
    • 这样比较灵活,所以以后他想要超过 3 个特定水果的时候……我也不认为公共成员是好的类设计。
    • 是的,但是你暴露了你的类的内部,这不是“正确的类设计”。还有其他方法可以在不暴露所有内容的情况下实现这一点。作为旁注,通过使用抽象类而不是接口,您仍然违反 DIP 原则,但这完全是另一回事;)(也许接口甚至不适用于 OP 的情况,没有足够的上下文)跨度>
    【解决方案4】:

    这是我解决问题的方法。与其在篮子里放一组预定义的水果,不如通过使用水果集合使其灵活。您将无法将它们传递给构造函数,而是通过方法添加它们。为此,每个水果类都继承了相同的接口(例如 IFruit)

    所以你有

    class Apple : IFruit { ... }
    class Orange : IFruit { ... }
    etc
    

    好处是您可以添加任意种类的水果,而不仅限于苹果、橙子和梨。

    水果篮可能看起来像这样

    public class FruitBasket {
        public FruitBasket() {
            _basket = new List<IFruit>();
        }
    
        public List<IFruit> Fruits {
            get { return _basket; }
        } 
    
        public AddFruit(IFruit fruit) {
            _basket.Add(fruit);
        }
    
        private readonly List<IFruit> _basket 
    }
    

    【讨论】:

    • 假设这是在 .Net/C# 中实现的,我建议在 IFruit 上使用抽象基类 (Fruit) 实现,然后是从 Fruit 派生的所有其他水果。
    • 您能否详细说明这种方法的优点?
    • 期望如果你有任何水果的通用功能(在这个“水果”案例中想不出任何功能),它可以移动到水果类。另一方面,接口迫使您没有任何实现。他们定义合同而不是功能。这在名为“框架设计指南”的书中得到了很好的解释
    • 我知道接口和抽象类的区别。我只是想知道为什么您建议使用抽象类而不是接口。但是在写评论和思考的时候,我想我自己想出了一个理由;-)
    【解决方案5】:

    大概FruitBasket 可以装任何类型的水果吧?这听起来像是多态性和依赖倒置的案例。

    class FruitBasket {
      readonly List<Fruit> _fruits = new List<Fruit>();
      public FruitBasket(params Fruit[] fruits) {
        _fruits.AddRange(fruits);
      }
    }
    interface Fruit {}
    class Apple : Fruit {}
    class Orange : Fruit {}
    

    在 OOP 中,您通常会传入依赖项。“预先知道”依赖项是什么更具程序性。

    【讨论】:

      【解决方案6】:

      我会设计不同的。

      class Fruit
      {
      }
      
      class Apple : Fruit
      { //Apple implementation here }
      
      class Orange : Fruit
      { //Orange implementation here }
      
      class Pear : Fruit
      { //Pear implementation here }
      
      class FruitBasket
      {
           Ilist<Fruit> Fruits;
      
           public FruitBasket()
           {
                Fruits =new List<Fruit>();
           }
      
           public AddFruit(Fruit)
           {
           // Add Fruit implementation here
           }
      
            public RemoveFruit(Fruit)
           {
           // Remove Fruit implementation here
           }
      
      }
      
      class Program
      {
           FruitBasket _fruitBasket;
      
           static void Main()
           {
               Fruit orange=new Orange(); 
               _fruitBasket = new FruitBasket();
               _fruitBasket.AddFruit(orange);
           }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多