OO分析--操作布置

OO Analysis--Operation placement

我很困惑,因为在识别 class 时我应该把 operation/function 放在哪里。下面的示例——摘自使用 UML、模式和 Java 进行面向对象设计的讲座幻灯片——特别让我感到困惑。 在此示例中,从用例描述的以下部分 "The customer enters the store to buy a toy" 中识别出 3 classes。 还标识了2个函数,一个是enters()(放在商店class),另一个是buy()(放置在玩具中 class)。

为什么这些功能与执行它们的客户无关?是否有任何启发式方法可以帮助操作放置?

你的例子非常简单,没有上下文很难说什么。无论如何,我会尽力回答你的问题。因此,首先:oo 建模不是以 "natural" 方式构建您的 classes。原因很简单:即使我们想对 "real world" 个对象建模,也根本不可能。现实世界(CustomerStoreToy)对象之间的关系几乎是无限复杂的。让我们考虑一下你的情况。顾客进店的时候,发生了很多事情,我们试试点单:

顾客进入商店

  1. 客户需要以某种方式与 "Store gateway" 互动,例如与门互动。甚至这种交互也可能很复杂:商店可能关门、满员、事故可能发生、门可能被挡住等等
  2. 当顾客最终进入商店时,可能有专门的商店政策来迎接顾客(或每一个第 n 位顾客)。我们还可以想象很多其他的东西。
  3. 最后,顾客想买一个玩具。首先,她需要找到那个玩具,这可能不是那么容易(你如何模拟这种互动?)。
  4. 当找到想要的玩具时,她需要把它拿走并加入购物车。
  5. 然后,顾客去排队等候。
  6. 等待结束后,顾客与收银员互动(很多小事,比如拿走玩具,查看价格,也许是一些快速聊天......)
  7. 最后,顾客可以为玩具付款(检查她是否有足够的钱,select付款方式(现金,卡,nfc?),离开队列...)。
  8. 客户离开商店(类似于 "enters a store" 交互,可能还有安全检查)。

我确定我忘记了什么。如您所见,简单的场景在现实世界中实际上非常复杂。这就是为什么不可能以完全相同的方式对其建模的原因。即使我们尝试过,天真的一对一映射也可能导致设计,其中几乎每个动作都是 Customer class 的方法:customer.enter()customer.leave()customer.buy()customer.findToy()customer.interactWithCashier()customer.openDoor()...等等。这种天真的映射将是完全糟糕的,因为 "Customer enters a store" 场景中的每一步实际上都是多个对象的协作,每个对象都以某种方式与另一个对象相连。另一方面,如果我们试图用所有交互来实现这个场景,我们将创建一个需要数年时间构建并且根本无法处理的系统(每次更改都需要疯狂的时间)。

好的,那么如何遵循良好的原则呢?只参与互动的一部分。不要试图以与现实世界中的工作方式完全相同的方式对其进行建模。尝试根据客户的需求调整模型。不要让您的 class 负担过重。每个 class 应该很容易理解,而且比较小。可以遵循一些软件建模的基本原则,比如SOLID、YAGNI。在实践中学习设计模式(找到一些 GOF 模式并尝试在您的项目中实现它们)。使用代码指标来分析您的代码(方法缺乏凝聚力、传出耦合、传入耦合、圈复杂度)以保持代码简单。

让我们回到您的具体示例。根据我之前提到的规则,对象建模非常重要的部分是将方法放在它们所属的地方。因此,数据和方法应该是 "coherent"(参见缺乏方法凝聚力指标)。所以,你的 classes 通常应该做一件事。例如,在您的示例中,Store class 的职责可能是允许客户购买玩具。所以,我们可以这样建模:

public class Store {

    public void buyToy(Toy toy, Customer customer) 
    throws ToyNotAvailableException, InsufficientFundsException {
        // some validation - check* methods are private
        if(!checkToyIsAvailable(toy)) {
            throw new ToyNotAvailableException();
        }
        if(!checkCustomerHasFunds(customer, toy.price())){
            throw new InsufficientFundsException();
        }
        // if validation succeeds, we can remove the toy from store
        // and charge the customer
        // removeFromStore is a private method      
        removeFromStore(toy);           
        customer.charge(toy.price());
    }

}

请记住,这只是一个简单的示例,旨在易于理解和阅读。我们应该对其进行多次优化以使其可以投入生产(例如处理付款方式、项目数量等)。