【问题标题】:Is this bad oop design?这是糟糕的 oop 设计吗?
【发布时间】:2011-08-01 14:18:37
【问题描述】:

我有一个名为 Chicken 的类,在 Chicken 中我有一些方法, 所以在另一个我实例化和调用Chicken方法的类中,我可能会做这样的事情:

Chicken chicken = new Chicken("Name","Description")


public void UpdateChicken(Chicken chicken)
{ 
   chicken.Update(chicken);
}

以上是否正常或是否存在问题,如果是,最好有另一个类,例如 ChickenCalculations 并执行以下操作:

public void UpdateChick(Chicken chicken)
{
    ChickenCalculations.Update(chicken);
}

这是一个实现:

Chicken chicken = new Chicken("Bob","Coolest Chicken", 4, 123, 5, 388, true, false, true);

Chicken anotherChicken = new Chicken()
anotherChicken.Update(chicken);
chicken.Update(chicken)

这是一个更实际的例子,而不是使用鸡:

public class AirlineBooking
{
    int BookingId {get;set;}
    string Name {get;set;}
    string Description {get;set;}
    decimal Price {get;set;}
    decimal Tax {get;set;}
    string seat {get;set;}
    bool IsActive {get;set;}
    bool IsCanceld {get;set;}


    public AirlineBooking(string name, string description, decimal price, 
                          decimal tax, string seat, bool isActive, bool isCanceled)
    {
        Name = name;
        Description = description;
        Price = price;
        Tax = tax;
        Seat = seat;
        IsActive = isActive;
        IsCanceled = isCanceled;
    }

    public Update(AirlineBooking airlineBooking, int id)
    {
          //Call stored proc here to update booking by id
    }

    public class BookingSystem
    {
       //Create new booking
       AirlineBooking booking = new AirlineBooking("ticket-1",
                                                   "desc",150.2,22.0,
                                                   "22A",true, false);

       //Change properties and update.
       booking.Name ="ticket-2";
       booking.Description = "desc2";
       booking.Price = 200.52;
       booking.Tax = 38.50;

       public void UpdateBooking(AirlineBooking booking, int id)
       {
            /* This is the meat of the question, should the passed in booking to
               update itself or should I have a Service Class , such as
               AirlineBookingOperations with an update method. */
            booking.Update(booking,id);
       }
    }
}

【问题讨论】:

  • 为什么将 chicken 本身传递给 Update 函数? chicken.Update() 还不够吗?
  • chicken.Update() 有什么问题?顺便说一句,您可能想提供一个更有意义的示例。当您处于无意义的领域时,很难讨论设计决策。
  • @Xaisoft:但是更新鸡是什么意思?如果不知道这意味着什么,很难说鸡是否可以自我更新。
  • 要记住的最重要的事情是不要在孵化之前更新您的鸡。

标签: c# .net oop design-patterns class-design


【解决方案1】:

为什么UpdateChicken 函数不是Chicken 类的成员?

这样,您不必传入 Chicken 对象的实例,而只需在现有实例上调用 Update 方法:

Chicken chicken = new Chicken("Name", "Description");
chicken.Update();

通常最好将操作特定类的所有方法封装在该类的内部,而不是将它们拆分为单独的“帮助器”类。让他们鸡自己管理吧!

【讨论】:

  • “为什么 UpdateChicken 函数不是 Chicken 类的成员”:可能是猪想要更新鸡? :)
  • @khachik:嗯,当然。但是你会给猪传递一个Chicken类的实例,他可以自己调用Update方法!
  • @Cody,将所有行为放在一个类中最终会导致非常大的类具有相互独立的相关行为块。解决这个问题是访客模式的原因之一。另一个例子是 WCF 类;通常使数据类完全轻量级并将操作 on 数据类分开(在服务中)。这允许您将相关行为分组到它自己的类中(并且可能在它自己的程序集中)。
  • @Kirk:是的,我说“通常”最好这样做。当涉及到设计模式时,我不相信“规则”或任何绝对的东西。我还指出,与Chicken 对象直接相关 的所有内容都应该在Chicken 类中。我仍然认为这是真的。将这些函数分解为辅助类很少是一个好主意。我更喜欢让我的模型简单直观,而不是试图围绕当今最新的模式/模型来思考。
  • 那个人说得好。阅读chicken.Update(chicken); 让我感到眩晕。
【解决方案2】:

面向对象编程的整个想法是认为对象能够对自己采取行动。

所以你应该只使用chicken.Update() 来更新鸡。

【讨论】:

    【解决方案3】:

    我将使用您的 AirlineBooking 类作为示例,因为很多人似乎对 Chicken 示例感到困惑。

    一些介绍:

    Single responsibility principle 声明一个对象应该有一个单一责任,并且它应该只关注与该责任相关的事物。例如,TaxCalculator 应该负责计算税款,而不是例如转换货币 - 这是 CurrencyConverter 的工作。

    这通常是一个非常好的主意,因为这意味着您的应用程序被构造成代码块,每个代码块都有一个单一的职责,使其更容易理解并且更安全地进行更改。另一种说法是,一个类或模块应该有一个并且只有一个改变的理由,例如“我们计算税收的方式已经改变”,或者“我们转换货币的方式已经改变”。


    你需要问自己的问题是:

    • AirlineBooking的职责是什么?
    • 更新航空公司预订是否属于该职责的一部分?

    例如在这种情况下,我会说AirlineBooking 的责任是“封装航空公司预订”,而更新航空公司预订实际上是预订系统的责任,而不是AirlineBooking

    另一种想法是,如果我将Update 方法放在AirlineBooking 上,这将意味着:

    • 如果预订系统更改为使用 Web 服务而不是存储过程,则需要更改 AirlineBooking 类。
    • 如果航空公司预订的封装发生变化(也许可以暂停预订,或者现在记录航空公司的名称),那么 AirlineBooking 需要更改。

    AirlineBooking 现在有很多不同的原因要更改,因此它不应该也负责“更新”


    简而言之,我可能会这样做:

    public class AirlineBooking
    {
        public int BookingId {get;set;}
        /* Other properties */
    }
    
    public class BookingSystem
    {
        public void UpdateBooking(AirlineBooking booking, int id)
        {
            // Call your SP here.
        }
    }
    

    您应该问自己这些问题的原因是因为它确实取决于 AirlineBooking 在您的应用程序中的用途。

    例如,如果AirlineBooking 是“知道”(即引用)预订系统,那么您可以添加一个“助手”方法,如下所示:

    public class AirlineBooking
    {
        public void Update(int id)
        {
            this.bookingSystem.UpdateBooking(this, id);
        }
    }
    

    【讨论】:

    • 我同意你的看法。我以前也是这样,后来改了。以鸡肉为例,似乎很多人无法集中注意力。谢谢你的帖子。我会再读一遍。
    【解决方案4】:

    你为什么不给你的鸡类一个方法“更新(一些参数...)”?然后,您可以通过

    实例化一只鸡
    Chicken chicken = new Chicken("Name", "descr");
    

    并通过以下方式更新:

    chicken.Update(myparameters..);
    

    编辑

    public class Chicken
    {
      public Chicken(string name, string description)
      {
         this.Name = name;
         this.Description = description;
      }
    
      public string Name { get; set; }
      public string Description { get; set; }
    
      // Fill in all the other properties!
    
      public int EggsDroppedInLife { get; set; }
    }
    

    现在您可以通过以下方式使用您的鸡类:

    Chicken chicken = new Chicken("Harry", "Nice chick");
    chicken.NumberOfEggs = 123;
    chicken.Description = "Oh no, it's actually not nice.";
    // ... change all the properties as you want
    

    【讨论】:

    • 如果 Chicken 有大量参数怎么办?
    • @Xaisoft - 一般来说,一个方法不应该有大量的参数。我会说最多 4-5 个参数是可以的,更多的都是有问题的。但是你可以拥有属性!如果你小鸡有10个属性,你可以一一设置。你知道属性是如何工作的吗?
    • @Xaisoft 如果 Chicken 有大量参数,您可以使用重载和默认参数。
    • 有时候我会搞不清楚是在Update方法中传入参数还是更新属性。你说的是更新鸡更新方法中的属性。
    • @Xaisoft - 等等。我将编辑我的帖子并做一个适当的例子。
    【解决方案5】:

    对象应该封装功能。应传入功能以赋予封装对象灵活性。

    因此,如果您要保存鸡肉,则应传入存储库功能。如果你有鸡计算,而且它们很容易改变,也应该传入。

    class Chicken
    {
       IChickenCalculations ChickenCalculations;
       IChickenRepository ChickenRepository;
       Chicken(IChickenCalculations chickenCalculations, IChickenRepository chickenRepository)
       {
           ChickenCalculations = chickenCalculations;
           ChickenRepository = chickenRepository ;
       }
    
       Calculate()
       {
           ChickenCalculations.Calculate(this);
       }
       Update()
       {
           ChickenRepository.Update(this);
       }
    }
    

    请注意,在此示例中,鸡如何能够对自身执行计算并对其自身进行持久化,而无需任何关于如何执行计算或持久化事物的知识(毕竟,它只是一只鸡)。

    【讨论】:

      【解决方案6】:

      虽然我意识到没有Chicken,但您的真实对象上可能有一个Update 方法,对吧?

      我认为您应该尝试在语言方面引入“更新”以外的其他内容。没有办法真正理解更新的作用。它只是更新鸡中的“数据”吗?在那种情况下,什么数据?还有,你应该被允许更新这样的 Chicken 实例吗?

      我宁愿看到像

      这样的东西
      chicken.CanFly = false;
      if(chicken.CanFly)  // inherited from Bird :)
          ckicken.FlyTo(point);
      else
          chicken.WalkTo(point);
      

      这是一个非常有趣的 OOP 练习:http://milano-xpug.pbworks.com/f/10080616-extreme-oop.pdf

      【讨论】:

      • '...了解更新的作用'。 必须赋予它更快条纹 - 可以考虑将其命名为PimpMyChick?
      • 哈哈.. 我真的很喜欢 Greg Youngish '介绍更多动词'的东西。虽然更新很一般。更新鸡时会发生什么?是整只鸡都更新了还是得到了新的翅膀?我认为这可能会受益于某些背景。
      • 不,只有条纹和大屏幕电视(挂在链子上);)
      【解决方案7】:

      对于多线程环境,拥有像 ChickenCalculations 这样的单独类更适合。当您需要执行除了 chicken.Update() 之外的其他一些步骤时,您可以使用 ChickenCalculations 类来完成。因此,如果在 Chicken 上实例化和调用方法的多个类不必担心 ChickenCalculations 类正在处理的相同事情。

      【讨论】:

        猜你喜欢
        • 2012-09-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-22
        相关资源
        最近更新 更多