哪种方法仅对实现接口的一个类可用,而对另一种则不可用,则是一种更好的方法?


11

基本上,我需要在特定条件下执行不同的操作。现有代码以这种方式编写

基本介面

// DoSomething.java
interface DoSomething {

   void letDoIt(String info);
}

实施第一类工人

class DoItThisWay implements DoSomething {
  ...
}

实施第二类工人

class DoItThatWay implements DoSomething {
   ...
}

主班

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          worker = new DoItThisWay();
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

该代码可以正常工作,并且易于理解。

现在,由于一项新要求,我需要传递一条仅对有意义的新信息DoItThisWay

我的问题是:以下编码样式是否可以很好地满足此要求。

使用新的类变量和方法

// Use new class variable and method

class DoItThisWay implements DoSomething {
  private int quality;
  DoSomething() {
    quality = 0;
  }

  public void setQuality(int quality) {
    this.quality = quality;
  };

 public void letDoIt(String info) {
   if (quality > 50) { // make use of the new information
     ...
   } else {
     ...
   }
 } ;

}

如果这样做,则需要对调用方进行相应的更改:

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          DoItThisWay tmp = new DoItThisWay();
          tmp.setQuality(quality)
          worker = tmp;

        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

这是一种好的编码风格吗?还是我可以抛弃它

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          worker = new DoItThisWay();
          ((DoItThisWay) worker).setQuality(quality)
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

19
为什么不只是传递quality给构造函数DoItThisWay呢?
David Arno

我可能需要修改我的问题...因为出于性能原因,DoItThisWayDoItThatWay的构造必须在的构造函数中完成一次MainMain是一个长期生活的阶层,doingIt一遍又一遍地被多次呼唤。
安东尼·孔

是的,在这种情况下,更新您的问题将是一个好主意。
David Arno

您是说setQualityDoItThisWay对象的生存期内将多次调用该方法吗?
jpmc26

1
您介绍的两个版本之间没有相关差异。从逻辑的角度来看,他们做同样的事情。
塞巴斯蒂安·雷德尔

Answers:


6

我假设quality需要在的每个letDoIt()调用旁边设置DoItThisWay()

我在这里看到的问题是:您正在引入时间耦合(即,如果setQuality()在调用?之前忘了调用letDoIt(),会发生DoItThisWay什么情况)。而对于实现DoItThisWayDoItThatWay是发散(一个需要有setQuality()所谓的,其他没有)。

虽然这可能暂时不会引起问题,但最终可能会再次困扰您。可能值得再看一遍,letDoIt()考虑是否需要将质量信息作为info传递给您的一部分。但这取决于细节。


15

这是一种好的编码风格吗?

从我的角度来看,您的两个版本都不是。必须先调用setQuality才能letDoIt调用的是时间耦合。您一直将其DoItThisWay视为的派生形式DoSomething,但并非(至少在功能上不是如此),而是类似

interface DoSomethingWithQuality {
   void letDoIt(String info, int quality);
}

这将使Main类似

class Main {
    // omitting the creation
    private DoSomething doSomething;
    private DoSomethingWithQuality doSomethingWithQuality;

    public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
            int quality = obtainQualityInfo();
            doSomethingWithQuality.letDoIt(info, quality);
        } else {
            doSomething.letDoIt(info);
        }
    }
}

另一方面,您可以将参数直接传递给类(假设这是可能的),并将决定使用哪个参数委托给工厂(这将使实例再次可互换,并且两者都可以从派生DoSomething)。这Main看起来像这样

class Main {
    private DoSomethingFactory doSomethingFactory;

     public doingIt(String info) {
         int quality = obtainQualityInfo();
         DoSomething doSomethingWorker = doSomethingFactory.Create(info, quality);
         doSomethingWorker.letDoIt();
     }
}

我知道你写的是

因为出于性能原因,DoItThisWay和DoItThatWay的构造在Main的构造函数中一次完成

但是您也可以缓存在工厂中创建成本很高的零件,并将它们也传递给构造函数。


啊,我输入答案时您在监视我吗?我们提出了类似的观点(但您的观点更为全面和雄辩);)
CharonX19年

1
@DocBrown是的,这值得商,,但是由于DoItThisWay在调用之前需要设置质量,letDoIt因此它的行为有所不同。我希望a DoSomething对所有衍生产品都以相同的方式工作(之前未设置质量)。这有意义吗?当然,这有点模糊,因为如果我们setQuality从工厂方法调用,客户将不确定是否必须设置质量。
Paul Kertscher

2
@DocBrown我认为合同letDoIt()是(缺少任何其他信息)“如果您提供给我,我可以正确执行(做我所做的一切)String info”。setQuality()事先要求被调用会加强调用的前提letDoIt(),因此会违反LSP。
CharonX

2
@CharonX:例如,如果有一个合同暗示“ letDoIt”不会抛出任何异常,而忘记调用“ setQuality”会letDoIt抛出异常,那么我会同意,这样的实现将违反LSP。但是这些对我来说似乎是非常人为的假设。
布朗

1
这是一个很好的答案。我要添加的唯一一件事是对代码库的糟糕时间耦合有多么灾难性的评论。这是看似去耦的组件之间的隐藏契约。使用普通耦合,至少它是显式且易于识别的。这样做实质上是在挖一个坑并用树叶覆盖它。
JimmyJames

10

假设quality无法将其传递到构造函数中,并且setQuality需要调用。

目前,类似

    int quality = obtainQualityInfo();
    worker = new DoItThisWay();
    ((DoItThisWay) worker).setQuality(quality);

太小了,无法投入太多的想法。恕我直言,它看起来有点难看,但并不是很难理解。

当这样的代码片段增加时,就会出现问题,并且由于重构,您最终会得到类似

    int quality = obtainQualityInfo();
    worker = CreateAWorkerForThisInfo();
    ((DoItThisWay) worker).setQuality(quality);

现在,您不会立即看到该代码是否仍然正确,并且编译器不会告诉您。因此,引入正确类型的临时变量并避免进行强制转换会更加类型安全,而无需付出任何额外的努力。

但是,我实际上会给tmp一个更好的名字:

      int quality = obtainQualityInfo();
      DoItThisWay workerThisWay = new DoItThisWay();
      workerThisWay.setQuality(quality)
      worker = workerThisWay;

这样的命名有助于使错误的代码看起来错误。


tmp甚至可以更好地命名为“ workerThatUsesQuality”之类的东西
user949300

1
@ user949300:恕我直言,这不是一个好主意:假定DoItThisWay获取更具体的参数-根据您的建议,每次都需要更改名称。最好使用名称vor变量来使对象类型清晰,而不是使用哪些方法名称。
布朗

为了澄清起见,它将是变量的名称,而不是类的名称。我同意将所有功能都放在类名中是一个糟糕的主意。所以我建议那样workerThisWayusingQuality。在那个小的代码段中,更具体一些是更安全的。(而且,如果有自己的域不是在一个更好的短语“usingQuality”使用它。
user949300

2

初始化根据运行时数据而变化的通用接口通常使其适合于工厂模式。

DoThingsFactory factory = new DoThingsFactory(thingThatProvidesQuality);
DoSomething doSomething = factory.getDoer(info);

doSomething.letDoIt();

然后,DoThingsFactory可以担心getDoer(info)在将具体的DoThingsWithQuality对象强制转换为DoSomething接口之前,在方法内部获取和设置质量信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.