使用流畅的接口重构长方法

发布于 2024-12-05 01:17:26 字数 1140 浏览 0 评论 0原文

我想知道您对使用流畅接口模式重构长方法的看法。

http://en.wikipedia.org/wiki/Fluent_interface

流畅模式不包含在重构书籍。

例如,假设您有一个很长的方法(名称很长) 做了很多事情)

class TravelClub {

   Receipt buyAndAddPointsAndGetReceipt(long amount, long cardNumber) {
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();

   }

   void buy(int amount) {...}

   void accumlatePoints(int cardNumber) {...}

   void generateRecepit() {...}

}

称为:

Receipt myReceipt = myTravelClub.buyAndAddPointsAndGetReceipt(543L,12345678L);

可以重构为:

class TravelClub {

   TravelClub buy(long amount) {
    //buy stuff
    return this;
   }

   TravelClub accumulatePoints(long cardNumber) {
    //accumulate stuff
    return this;
   }

   Receipt generateReceipt() {
    return new Receipt(...);
   }


}

并称为:

Receipt myReceipt = myTravelClub.buy(543L).accumulatePoints(12345678L).generateReceipt();

从我的角度来看,这是分解长方法的一种很好的方式,并且 亦分解其名。

你怎么认为?

I'd like to know your opinion about using the fluent interface pattern to refactor a long method.

http://en.wikipedia.org/wiki/Fluent_interface

The fluent pattern is not included in the refactoring books.

for example, say you have this long method (with a long name as it
does many things)

class TravelClub {

   Receipt buyAndAddPointsAndGetReceipt(long amount, long cardNumber) {
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();

   }

   void buy(int amount) {...}

   void accumlatePoints(int cardNumber) {...}

   void generateRecepit() {...}

}

called as:

Receipt myReceipt = myTravelClub.buyAndAddPointsAndGetReceipt(543L,12345678L);

That could be refactored to:

class TravelClub {

   TravelClub buy(long amount) {
    //buy stuff
    return this;
   }

   TravelClub accumulatePoints(long cardNumber) {
    //accumulate stuff
    return this;
   }

   Receipt generateReceipt() {
    return new Receipt(...);
   }


}

and called as:

Receipt myReceipt = myTravelClub.buy(543L).accumulatePoints(12345678L).generateReceipt();

from my point of view this is a quite good manner to decompose a long method and
also to decompose its name.

what do you think?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

绅刃 2024-12-12 01:17:26

它有一个问题,因为您必须记住积累积分并执行购买(并生成收据,这不是什么问题,因为我认为该操作没有副作用)。在我看来,积分应该在购买时自动累积。您在购买时收到收据也是很自然的,因此在某种程度上,您最初的方法很好,只是读起来不太好。

如果您想要一个流畅的界面,我会引入一个额外的类,它会温和地引导客户端代码做正确的事情(假设所有购买都是通过卡进行的,并以相同的方式累积积分):

class TravelClub {

   OngoingPurchase buyAmount(long amount) {
      return new OngoingPurchase(amount);
   }

   private Receipt buyAndAddPointsAndGetReceipt(long amount, long cardNumber){
      // make stuff happen
   }

   public class OngoingPurchase {
      private final long amount;
      private OngoingPurchase(long amount){
         this.amount = amount;
      }
      public Receipt withCard(long cardNumber){
         return buyAndAddPointsAndGetReceipt(long amount, cardNumber);
      }
   }

}

// Usage:
Receipt receipt = travelClub.buyAmount(543).withCard(1234567890L);

这样,如果您忘记调用 < code>withCard,什么也没有发生。发现丢失的交易比发现错误的交易更容易,而且如果不执行完整的交易,您就无法获得收据。

编辑:顺便说一句,有趣的是,我们所做的所有这些工作都是为了使具有许多参数的方法可读,而例如命名参数将使问题完全消失:

Receipt r = travelClub.makePurchase(forAmount: 123, withCardNumber: 1234567890L);

It has a problem in that you have to remember both to accumulate the points and perform the purchase (and generate the receipt, which is less of a problem as I assume that action has no side effects). In my mind, point accumulation should come automatically when performing a purchase. It's also rather natural that you get a receipt when performing a purchase, so in a way, your initial method was fine, except that it doesn't read very well.

If you want a fluent interface I'd introduce an extra class which gently guides the client code into doing the right thing (assuming that all purchases happen with a card and accumulate points the same way):

class TravelClub {

   OngoingPurchase buyAmount(long amount) {
      return new OngoingPurchase(amount);
   }

   private Receipt buyAndAddPointsAndGetReceipt(long amount, long cardNumber){
      // make stuff happen
   }

   public class OngoingPurchase {
      private final long amount;
      private OngoingPurchase(long amount){
         this.amount = amount;
      }
      public Receipt withCard(long cardNumber){
         return buyAndAddPointsAndGetReceipt(long amount, cardNumber);
      }
   }

}

// Usage:
Receipt receipt = travelClub.buyAmount(543).withCard(1234567890L);

This way, if you forgot to call withCard, nothing happens. It's easier to spot a missing transaction than an incorrect transaction, and you can't get a receipt without performing a complete transaction.

Edit: As an aside, it's funny to think that we do all this work to make methods with many parameters readable, when for example named parameters would make the problem go away completely:

Receipt r = travelClub.makePurchase(forAmount: 123, withCardNumber: 1234567890L);
菩提树下叶撕阳。 2024-12-12 01:17:26

,预期的行为是什么

myTravelClub.accumulatePoints(10000000L);

那么我的反问题是,如果有人打电话而不打电话买 ?或者在购买前生成收据?我认为流畅的界面仍然需要遵守其他面向对象的约定。如果您确实想要一个流畅的界面,那么 buy() 方法必须返回另一个对象,不是 TravelClub 本身,而是一个具有 accumulatePoints() 的“购买对象” code> 和 generateReceipt() 方法。

也许我对你的示例的语义读得太多了,但是维基百科示例具有逻辑上可以按任何顺序调用的方法是有原因的。我认为 Hibernate criteria API 是另一个很好的例子。

My counter-question is then, what is the expected behavior if someone instead calls:

myTravelClub.accumulatePoints(10000000L);

without calling buy? Or generating the receipt before the purchase? I think that fluent interfaces still need to adhere to other OO conventions. If you really want a fluid interface, then the buy() method would have to return another object, not the TravelClub itself, but a "purchase object" that has the accumulatePoints() and generateReceipt() methods.

Maybe I am reading to much into the semantics of your example, but there is a reason why the wikipedia example has methods that logically can be called in any order. I think the Hibernate criteria API is another good example.

你曾走过我的故事 2024-12-12 01:17:26

长方法与具有长名称的方法不同。就您而言,我唯一要更改的是方法名称:(

public Receipt buy(long amount, long cardNumber) {
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();
}

并为私有 buy 方法考虑一个更具描述性的名称),因为所有三件事(“buying”、accumulatePoints 和获取收据)总是一起发生,因此从调用代码的角度来看,它们可以是单个操作。从实施的角度来看,单一操作也更容易。吻:-)

A long method is not the same as a method with a long name. In your case, the only thing I'd change is the method name:

public Receipt buy(long amount, long cardNumber) {
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();
}

(and think of a more descriptive name for the private buy method) because all three things ("buying", accumulatePoints and getting the receipt) always happen together, so from the view of calling code, they can be a single operation. From an implementation perspective, having a single operation is easier, too. KISS :-)

春风十里 2024-12-12 01:17:26

使用单一方法的优点是始终调用相同的序列。例如,您不能像在您提供的流畅界面示例中那样跳过accumulatePoints。

如果调用这些方法的唯一方法与第一个代码块中的顺序相同,请将其保留为单个函数。但是,如果在生成收据之前可以在 TravelClub 上完成任何操作子集,那么请务必使用流畅的界面。这是克服“组合爆炸”代码味道的最佳方法之一(如果不是最好的话)。

The advantage of using a single method is that the same sequence is always called. e.g., you can't skip accumulatePoints like you could in the fluent-interface example you provided.

If the only way to call these methods would be in the same sequence as in your first block of code, keep it as a single function. If, however, any subset of manipulations can be done on TravelClub before a receipt is generated, then by all means use a fluent interface. This is one of the best ways (if not the best) to overcome the 'combinitorial explosion' code smell.

挽清梦 2024-12-12 01:17:26

只要您使用了正确的验证,Fluent 接口就更容易理解,例如它可以如下所示:

class TravelClub {

   TravelClub buy(long amount) {
    buy(amount);
    return this;
   }

   TravelClub accumulatePoints(long cardNumber) {
    if (!bought)
    {
        throw new BusinessException("cannot accumulate points if not bought");
    }
    accumulatePoints(cardNumber);
    return this;
   }

   Receipt generateReceipt() {
    if (!bought)
    {
       throw new BusinessException("cannot generate receipts not bought");
    }
    return new Receipt(...);
   }
}

As long as you have used proper validations, Fluent interfaces are much more easier to understand, for example it could be like follows,

class TravelClub {

   TravelClub buy(long amount) {
    buy(amount);
    return this;
   }

   TravelClub accumulatePoints(long cardNumber) {
    if (!bought)
    {
        throw new BusinessException("cannot accumulate points if not bought");
    }
    accumulatePoints(cardNumber);
    return this;
   }

   Receipt generateReceipt() {
    if (!bought)
    {
       throw new BusinessException("cannot generate receipts not bought");
    }
    return new Receipt(...);
   }
}
感受沵的脚步 2024-12-12 01:17:26

在我看来,这里的部分困难在于选择一个包含方法所做的一切的良好描述性名称。问题自然是,有时您有很多复杂的逻辑,无法用简单的名称轻松描述。

在您的代码示例中提出的情况下,我很想将方法本身的名称简化为更通用的名称:

Receipt Transaction(long amount, long cardNumber) 
{
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();
}

那么我提到的这个逻辑问题又如何呢?这本身可以归结为你的方法在其功能上是否非常固定。如果只能使用购买->积分->收据序列来完成交易,则可以使用更简单的名称,但更具描述性的名称也可以,并且流畅的界面可能是一个合理的选择。

如果客户没有奖励卡或不希望有收据怎么办?如果在一次交易中可能会购买多个商品,那么情况又如何呢?当然,假设 buy 方法可能代表一个购买商品,而不仅仅是在其他地方计算出的总数?一旦你开始在序列中引入问题/选择,设计就会变得不太明显,命名也会变得更加困难。您当然不想使用疯狂的长名称,例如:

BuyAndAddPointsIfTheCustomerHasACardAndReturnAReceiptIfTheCustomerAsksForIt(...)

当然,它确切地告诉您它的作用,但它也突出了一个潜在的问题,即该方法可能负责太多的事情,或者它可能隐藏了一个它调用的方法之一背后有更复杂的代码味道。同样,像“Transaction”这样的简单方法名称可能会过度简化需要更好理解的复杂问题。

流畅的界面在这里可以带来很大的好处,只要它能够指导开发人员就如何应用所调用的流畅方法做出明智的决定。如果调用序列很重要,则需要将返回类型限制为序列中的下一个选择。如果调用顺序不太重要,那么您可以使用具有更通用接口的返回类型,该接口允许选择以任何顺序调用的方法。

至于是否使用流畅的接口,我认为它不应该仅仅作为分解难以命名的方法的一种手段来决定。您正在做出一个设计选择,您需要在产品的整个生命周期中接受它,并且从维护的角度来看,我发现流畅的界面会使设计更难以在您的应用程序中可视化、组织和维护。代码。最终,您需要决定这是否是您可以忍受的东西,以权衡它给您带来的好处。对我来说,我通常首先询问用例组合是否固定且简单,或者它们是否相对无穷无尽。如果是后者,流畅的界面可能有助于保持代码更简洁,并且更易于在多种场景中使用。我还会考虑代码是否属于更通用的层(例如 API),或者更专业的层(例如,流畅的界面可能会很好地工作)。

It seems to me that part of the difficulty here is in choosing a good descriptive name that encompasses everything that a method does. The problem naturally is that sometimes you have a lot of complicated logic that you can't easily describe with a simple name.

In the case posed in your code example, I'd be tempted to simplify the name of the method itself to something a little more generalized:

Receipt Transaction(long amount, long cardNumber) 
{
    buy(amount);
    accumulatePoints(cardNumber);
    return generateReceipt();
}

So what about this logic problem I mentioned? That itself boils down to whether or not your method is very fixed in what it does. If it is only possible to complete the transaction using the Buy->Points->Receipt sequence, then a simpler name works, but so does the more descriptive name, and a fluent interface may be a reasonable alternative.

What about those cases where the customer doesn't have a rewards card, or doesn't wish to have a receipt? What about those situations where several items might be purchased in a single transaction - assuming of course that the buy method might represent a purchase item and not simply a total that was calculated elsewhere? Once you start introducing questions/choices into the sequence, the design becomes a little less obvious and the naming a lot harder. You certainly wouldn't want to use a crazy long name like:

BuyAndAddPointsIfTheCustomerHasACardAndReturnAReceiptIfTheCustomerAsksForIt(...)

Sure, it tells you exactly what it does, but it also highlights a potential problem in that the method is possibly responsible for too many things, or that it might be hiding a more complex code smell behind one of the methods that it calls. Likewise a simple method name like "Transaction" could be oversimplifying a complex problem that needs to be better understood.

A fluent interface can be of great benefit here provided it guides the developer to make sensible decisions about how to apply the fluent methods being called. If the calling sequence is important, you need to restrict the return types to the next choices in the sequence. If the calling sequence is less important, then you can use a return type with a more generalized interface that allows a selection of methods to be called in any sequence.

As to whether or not to use a fluent interface at all, I don't think it should be decided merely as a means to decompose difficult to name methods. You are making a design choice that you are going to need to live with for the lifetime of the product, and from a maintenance perspective, I've found that fluent interfaces can make the design more difficult to visualize and to organize and maintain in your code. Ultimately you need to decide if this is something you can live with as a trade-off against the benefits it gives you. For me, I usually start by asking if the use-case combinations are fixed and simple, or if they are relatively endless. If the latter, a fluent interface may help to keep your code cleaner and easier to use in multiple scenarios. I would also consider whether the code belongs to a more generalized layer such as an API for example where a fluent interface may work nicely, or something more specialized.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文