学習デザインパターン(VIII)の美しさは:なぜそれほど継承の多目的組み合わせであると言われて?どのようにどの組み合わせまたは継承によって決めるのですか?

優れた継承の組み合わせ、以下の継承の組み合わせで:オブジェクト指向プログラミングでは、あること、非常に古典的なデザインの原則があります。なぜ継承を使用することは推奨されませんか?継承の組み合わせに比べて利点は何ですか?どの組み合わせで相続またはを決定するには?

なぜ継承を使用することは推奨されませんか?

継承は、クラス間で表現するために、オブジェクト指向の4つの主要な特徴の一つであるis-a、関係コードの再利用の問題を解決することができます。役割の継承の多くが、継承階層が深すぎるありますが、あまりにも複雑で、また、コードの保守性に影響を与えることができます。だから、あなたがプロジェクトに継承を使用する必要があるかどうか、多くのオンライン論争があります。多くの人々は、その継承がさえなくて、慎重に使用する必要があり、アンチパターンだと思います。なぜ論争?

私たちは鳥についてのクラスを設計するとします。私たちは、抽象クラスとして定義された「鳥」などAの事抽象的な概念、意志AbstractBirdなどスズメ、ハト、カラスなどの鳥のすべての複数のセグメントは、抽象クラスを継承します。

私たちは、その後で、私たちすることができ、ほとんどの鳥が飛ぶことができることを知っているAbstractBird抽象クラスは、定義するfly()ことを行う方法を?答えはノーです。ほとんどの鳥が飛ぶことができますが、しかし、ダチョウは飛べないんなど、例外もあります。ダチョウは継承しているfly()、それはダチョウが現実世界の物事の理解と一致して明確ではない、そのような行動を、「飛ぶ」だろう、親クラスのメソッドを。もちろん、あなたが、このサブクラス(に書き換えダチョウ言うかもしれないoverridefly()方法を、それがスローUnSupportedMethodExceptionそれを行うことができない例外?次のように特定のコードの実装は次のとおりです。

public class AbstractBird {
  //...省略其他属性和方法...
  public void fly() { //... }
}

public class Ostrich extends AbstractBird { //鸵鸟
  //...省略其他属性和方法...
  public void fly() {
    throw new UnSupportedMethodException("I can't fly.'");
  }
}

このデザインのアイデアは、問題を解決するため、あまり美しくすることができますが。ダチョウ、飛べない鳥に加えて、ペンギンなど、多くありますので。これら飛べ鳥のために、書き換えの必要性fly()メソッドは例外をスローします。この設計は、片手、およびコードの作業負荷を招く上、他方で、また最小知識の原則に反している(Least Knowledge Principleまた、少なくとも知識やデメテルの原理と呼ばれる)、露光が外部インタフェースに曝されるべきではなく、増加しますクラス確率は、使用中に誤用しました。

ていることをAbstractBird派生クラス2つのより抽象クラスのセグメント:鳥が飛んでAbstractFlyableBirdと飛べ鳥はAbstractUnFlyableBird、これらの鳥が継承されているスズメは、カラスが飛ぶのでAbstractFlyableBird、これらの飛べない鳥ダチョウ、ペンギンを聞かせて、継承されたAbstractUnFlyableBirdクラスは、あなたが行うことはできませんか?以下に示すように、特定の継承:
image.png
としては、三継承に、図から分かります。しかし、全体的に、現在の継承は比較的浅いレベル、比較的簡単で、許容可能なデザインのアイデアを考えることができます。そして、少し難しく追加していきます。ちょうどこのシナリオでは、我々は唯一の「飛べないだろう鳥」を懸念しているが、私たちも心配している場合は、この時間は、どのクラスのそれとの間に継承関係を設計するという「鳥は、呼び出されますか」?

飛ぶかどうか?あなたは呼びますか?二つは、彼らと一緒に働き4例があります:ハエが呼び出されます、飛ばない呼び出します、ハエが呼び出されることはありません飛ばないだろう、と呼ばれることはありません。私たちはデザインのアイデアに従うことを続けるならば、それは4つの抽象クラスを定義する必要があります(AbstractFlyableTweetableBird、、 、)。AbstractFlyableUnTweetableBird あなたも、このようなAの振る舞いを「それが卵を産むかどうか」を検討する必要がある場合、組み合わせが爆発であろうと推定されています。クラス継承階層は、継承関係はますます複雑になり、深く深くなります。そして、このレベルは非常に深いです、非常に複雑な継承、一方では、あまり読みやすいコードにつながります。私たちは、メソッドは、クラス、プロパティを持っているかを把握する必要があるので、あなたは......コード最上位の親クラスへの道バックを親、親クラスの親クラスのコードのコードを読み取る必要があります。一方、それはまた、このようなパッケージの特性を破壊し、実装の詳細は、親クラスのサブクラスにさらされることになります。親クラス実装依存の実装サブクラス、カップリングの高さの両方が、親一度コードの変更は、すべてのロジックサブクラスに影響を与えます。AbstractUnFlyableTweetableBirdAbstractUnFlyableUnTweetableBird
image.png

要するに、最大の問題は、その相続です:継承階層が深すぎ、複雑すぎる継承は、コードの可読性と保守性に影響を与えます。継承が推奨されていないのはこのためです。継承された問題のそれだけの例は、どのようにそれを解決するには?

継承の組み合わせに比べて利点は何ですか?

実際には、(の組み合わせを使用することができますcomposition)、インタフェース、代表者(delegation)だけ継承の問題を一緒に解決するための3つの技術的手段を。

前面讲到接口的时候说过,接口表示具有某种行为特性。针对“会飞”这样一个行为特性,我们可以定义一个 Flyable 接口,只让会飞的鸟去实现这个接口。对于会叫、会下蛋这些行为特性,我们可以类似地定义 Tweetable 接口、EggLayable 接口。将这个设计思路翻译成 Java 代码的话,就是下面这个样子:

public interface Flyable {
  void fly();
}
public interface Tweetable {
  void tweet();
}
public interface EggLayable {
  void layEgg();
}
public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  //... 省略其他属性和方法...
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}
public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀
  //... 省略其他属性和方法...
  @Override
  public void fly() { //... }
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}

不过,接口只声明方法,不定义实现。也就是说,每个会下蛋的鸟都要实现一遍 layEgg() 方法,并且实现逻辑是一样的,这就会导致代码重复的问题。那这个问题又该如何解决呢?

可以针对三个接口再定义三个实现类,它们分别是:实现了 fly() 方法的 FlyAbility 类、实现了 tweet() 方法的 TweetAbility 类、实现了 layEgg() 方法的 EggLayAbility 类。然后,通过组合和委托技术来消除代码重复。具体的代码实现如下所示:

public interface Flyable {
  void fly();
}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  private TweetAbility tweetAbility = new TweetAbility(); //组合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
  //... 省略其他属性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委托
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委托
  }
}

继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用。而这三个作用都可以通过其他技术手段来达成。比如 is-a 关系,我们可以通过组合和接口的 has-a 关系来替代;多态特性我们可以利用接口来实现;代码复用我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

如何判断该用组合还是继承?

尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。从上面的例子来看,继承改写成组合意味着要做更细粒度的类的拆分。这也就意味着,我们要定义更多的类和接口。类和接口的增多也就或多或少地增加代码的复杂程度和维护成本。所以,在实际的项目开发中,我们还是要根据具体的情况,来具体选择该用继承还是组合。

如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。

除此之外,还有一些设计模式会固定使用继承或者组合。比如,装饰者模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。

前面讲到继承可以实现代码复用。利用继承特性,我们把相同的属性和方法,抽取出来,定义到父类中。子类复用父类中的属性和方法,达到代码复用的目的。但是,有的时候,从业务含义上,A 类和 B 类并不一定具有继承关系。比如,Crawler 类和 PageAnalyzer 类,它们都用到了 URL 拼接和分割的功能,但并不具有继承关系(既不是父子关系,也不是兄弟关系)。仅仅为了代码复用,生硬地抽象出一个父类出来,会影响到代码的可读性。如果不熟悉背后设计思路的同事,发现 Crawler 类和 PageAnalyzer 类继承同一个父类,而父类中定义的却只是 URL 相关的操作,会觉得这个代码写得莫名其妙,理解不了。这个时候,使用组合就更加合理、更加灵活。具体的代码实现如下所示:

public class Url {
  //...省略属性和方法
}

public class Crawler {
  private Url url; // 组合
  public Crawler() {
    this.url = new Url();
  }
  //...
}

public class PageAnalyzer {
  private Url url; // 组合
  public PageAnalyzer() {
    this.url = new Url();
  }
  //..
}

还有一些特殊的场景要求我们必须使用继承。如果你不能改变一个函数的入参类型,而入参又非接口,为了支持多态,只能采用继承来实现。比如下面这样一段代码,其中 FeignClient 是一个外部类,我们没有权限去修改这部分代码,但是我们希望能重写这个类在运行时执行的 encode() 函数。这个时候,我们只能采用继承来实现了。

public class FeignClient { // feign client框架代码
  //...省略其他代码...
  public void encode(String url) { //... }
}

public void demofunction(FeignClient feignClient) {
  //...
  feignClient.encode(url);
  //...
}

public class CustomizedFeignClient extends FeignClient {
  @Override
  public void encode(String url) { //...重写encode的实现...}
}

// 调用
FeignClient client = new CustomizedFeignClient();
demofunction(client);

尽管有些人说,要杜绝继承,100% 用组合代替继承,但是这里的观点没那么极端!之所以“多用组合少用继承”这个口号喊得这么响,只是因为,长期以来,过度使用继承。还是那句话,组合并不完美,继承也不是一无是处。只要我们控制好它们的副作用、发挥它们各自的优势,在不同的场合下,恰当地选择使用继承还是组合,这才是我们所追求的境界。

重点回顾

1. 为什么不推荐使用继承?

继承是面向对象的四大特性之一,用来表示类之间的 is-a 关系,可以解决代码复用的问题。虽然继承有诸多作用,但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少用,甚至不用继承。

2. 组合相比继承有哪些优势?

继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。

3. 如何判断该用组合还是继承?

尽管我们鼓励多用组合少用继承,但组合也并不是完美的,继承也并非一无是处。在实际的项目开发中,我们还是要根据具体的情况,来选择该用继承还是组合。如果类之间的继承结构稳定,层次比较浅,关系不复杂,我们就可以大胆地使用继承。反之,我们就尽量使用组合来替代继承。除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。

思考

  • ベースMVCアーキテクチャの開発Web、多くの場合、データベース・レベルで定義されたアプリケーションの時間Entityに、Serviceビジネス層の定義BOBusiness ObjectにおけるController界面層の定義VOView Object)。ほとんどの場合、EntityBOVO大きく3回の反復の間にコードが、同一ではありません。どのように対処するためにEntityBOVOコードの重複の問題?

リファレンスは:なぜそれほど継承の多目的組み合わせであると言われて?どのようにどの組み合わせまたは継承によって決めるのですか?

ブログ記事複数のプラットフォームからこの記事OpenWriteリリース!

おすすめ

転載: www.cnblogs.com/muchen-li/p/11940523.html