这是不好的面向对象设计吗?

21

我有一个名为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);
       }
    }
}

6
为什么要将鸡本身传递给Update函数?chicken.Update()不足以吗? - Alessandro
3
chicken.Update() 有什么问题?顺便提一下,你可能想提供一个更有意义的例子。当你处于一个毫无意义的领域时,很难讨论设计决策。 - R. Martinho Fernandes
2
@Xaisoft:但是“更新一只鸡”意味着什么?不知道这个意思,很难说一只鸡自己更新是否可以。 - R. Martinho Fernandes
9
记住最重要的一点是不要在小鸡孵化之前更新它们。 - Cody Gray
3
http://plif.courageunfettered.com/archive/wc072.gif - Eric Lippert
显示剩余15条评论
7个回答

26

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

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

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

通常最好将操作一个特定类的所有方法封装在该类内部,而不是将它们拆分成单独的“helper”类。让这些对象自己管理自己!


5
为什么UpdateChicken函数不是Chicken类的成员函数:可能是因为猪想要更新一只鸡? :) - khachik
2
@khachik:当然。但是那样你会把一个“Chicken”类的实例传递给这只猪,然后它自己可以调用“Update”方法! - Cody Gray
9
@Cody,将所有行为放在一个类中最终可能会导致非常庞大的类,其中互相独立的相关行为块。解决这个问题是访问者模式的原因之一。另一个例子是WCF类;通常将数据类完全轻量化,并将操作on数据类分开(在服务中)。这允许您将相关行为分组到其自己的类中(并可能在其自己的程序集中)。 - Kirk Woll
5
@Kirk:是的,我说过这通常是最好的做法。但我不相信在设计模式方面有任何绝对的“规则”。我还指出了一点,即与“Chicken”对象直接相关的所有内容都应该放在“Chicken”类内部。我仍然认为这是正确的。把这些函数分解到一个辅助类中很少是一个好主意。我更喜欢保持我的模型简单易懂,而不是试图掌握最新的模式/模型。 - Cody Gray
3
说得好。读到 chicken.Update(chicken); 让我晕眩了。 - StuperUser
显示剩余3条评论

9
整个面向对象编程的思想是将对象视为能够自我操作的实体。
因此,您只需使用chicken.Update()来更新一个鸡。

8

我将以您的AirlineBooking类为例,因为很多人似乎在Chicken示例上混淆了自己。

一些介绍:

单一责任原则指出一个对象应该有单一职责,并且它只应关注与该职责密切相关的事情。例如,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);
    }
}

我同意你的观点。我以前也是这样做的,但后来改变了。似乎很多人无法集中精力,以鸡肉为例子。感谢您的帖子。我会再次阅读它。 - Xaisoft

3
为什么不给你的 Chicken 类添加一个方法 "Update(some parameters...)"?这样,你就可以通过实例化一只鸡来使用它。
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 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

@Xaisoft 如果Chicken有大量的参数,您可以使用重载和默认参数。 - patrick
@Christian:外部如何将eggsDroppedInLife设置为鸡? - khachik
我该如何决定是将参数传递给更新Chicken属性还是直接更新属性而不传递参数? - Xaisoft
@Christian:所以你有一个带有公共setpublic int EggsDroppedInLife。这意味着有人可以为一只鸡设置EggsDroppedInLife。从技术上讲不是,但从逻辑上讲,这是缺乏封装性。 - khachik
请问您能否回答这个问题:http://stackoverflow.com/questions/9511137/is-the-solution-design-optimal-c-sharp-3-tier? - LCJ
显示剩余7条评论

3

对象应该封装功能。为了给封装对象提供灵活性,功能应该被传递进去。

因此,如果你正在保存鸡肉,应该传递存储库功能。如果你有鸡肉计算,并且它们容易改变,也应该传递进去。

class Chicken
{
   IChickenCalculations ChickenCalculations;
   IChickenRepository ChickenRepository;
   Chicken(IChickenCalculations chickenCalculations, IChickenRepository chickenRepository)
   {
       ChickenCalculations = chickenCalculations;
       ChickenRepository = chickenRepository ;
   }

   Calculate()
   {
       ChickenCalculations.Calculate(this);
   }
   Update()
   {
       ChickenRepository.Update(this);
   }
}

请注意,在此示例中,这只鸡能够对自己进行计算并保持自身状态,而不需要知道如何进行计算或保存东西(毕竟,它只是一只鸡)。


1

虽然我知道没有 Chicken ,但你的真实对象上可能有一个 Update 方法,对吧?

我认为你应该尝试引入一些不同于“更新”的语言。其实不太清楚更新到底是做什么。它是否只是更新了Chicken中的“数据”?那具体是哪些数据呢?而且,你是否允许像这样更新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


“...理解 update 函数的作用”。必须给它加上加速条纹 - 可以考虑将其命名为 PimpMyChick - Grant Thomas
哈哈,我对Greg Youngish的“引入更多动词”的事情非常感兴趣。更新相当通用。当你更新一只鸡时会发生什么?是整只鸡都会更新还是它会得到新的翅膀?不过我认为这可能需要一些上下文来解释。 - Mikael Östberg
不,除了条纹和一台大屏幕电视(挂在链子上)之外,没有其他东西。 ;) - Grant Thomas

0

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


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