Javaの継承と構成(Is-a対Has-aの関係)
1. 概要
Inheritanceと構成は、抽象化、カプセル化、およびポリモーフィズムとともに、object-oriented programming(OOP)の基礎です。
このチュートリアルでは、継承と構成の基本について説明し、2つのタイプの関係の違いを見つけることに重点を置きます。
2. 継承の基本
継承は、強力でありながら使いすぎで誤用されているメカニズムです。
単純に、継承を使用して、基本クラス(a.k.a. 基本型)は、特定の型に共通の状態と動作を定義し、サブクラス(別名、 サブタイプ)は、その状態と動作の特別なバージョンを提供します。
継承の操作方法を明確にするために、単純な例を作成しましょう。人の共通フィールドとメソッドを定義する基本クラスPersonと、サブクラスWaitressとActressです。 ■追加のきめ細かいメソッド実装を提供します。
Personクラスは次のとおりです。
public class Person {
private final String name;
// other fields, standard constructors, getters
}
そして、これらはサブクラスです。
public class Waitress extends Person {
public String serveStarter(String starter) {
return "Serving a " + starter;
}
// additional methods/constructors
}
public class Actress extends Person {
public String readScript(String movie) {
return "Reading the script of " + movie;
}
// additional methods/constructors
}
さらに、WaitressクラスとActressクラスのインスタンスがPersonのインスタンスでもあることを確認する単体テストを作成して、「is-a」条件がタイプレベル:
@Test
public void givenWaitressInstance_whenCheckedType_thenIsInstanceOfPerson() {
assertThat(new Waitress("Mary", "[email protected]", 22))
.isInstanceOf(Person.class);
}
@Test
public void givenActressInstance_whenCheckedType_thenIsInstanceOfPerson() {
assertThat(new Actress("Susan", "[email protected]", 30))
.isInstanceOf(Person.class);
}
It’s important to stress here the semantic facet of inheritance。 Person classの実装を再利用する以外に、ベースタイプPersonとサブタイプWaitressおよびActressの間のwe’ve created a well-defined “is-a” relationship。 ウェイトレスと女優は、事実上、人です。
これにより、次のように尋ねられる場合があります:in which use cases is inheritance the right approach to take?
サブタイプが「is-a」条件を満たす場合、主にクラス階層のさらに下の追加機能を提供します。 次に、継承が進むべき道です。
もちろん、オーバーライドされたメソッドがLiskov Substitution Principleによって促進された基本タイプ/サブタイプの置換可能性を保持している限り、メソッドのオーバーライドは許可されます。
さらに、the subtypes inherit the base type’s APIは、やり過ぎまたは単に望ましくない場合があることを覚えておく必要があります。
それ以外の場合は、代わりに構成を使用する必要があります。
3. デザインパターンの継承
コンセンサスは、可能な限り継承よりも合成を優先すべきだということですが、継承が代わりになる典型的なユースケースがいくつかあります。
3.1. レイヤースーパータイプパターン
この場合、use inheritance to move common code to a base class (the supertype), on a per-layer basisです。
ドメインレイヤーでのこのパターンの基本的な実装は次のとおりです。
public class Entity {
protected long id;
// setters
}
public class User extends Entity {
// additional fields and methods
}
サービスレイヤーや永続化レイヤーなど、システム内の他のレイヤーにも同じアプローチを適用できます。
3.2. テンプレートメソッドパターン
テンプレートメソッドパターンでは、use a base class to define the invariant parts of an algorithm, and then implement the variant parts in the subclassesを実行できます。
public abstract class ComputerBuilder {
public final Computer buildComputer() {
addProcessor();
addMemory();
}
public abstract void addProcessor();
public abstract void addMemory();
}
public class StandardComputerBuilder extends ComputerBuilder {
@Override
public void addProcessor() {
// method implementation
}
@Override
public void addMemory() {
// method implementation
}
}
4. 作曲の基本
構成は、実装を再利用するためにOOPによって提供される別のメカニズムです。
一言で言えば、composition allows us to model objects that are made up of other objectsは、したがって、それらの間の「has-a」関係を定義します。
さらに、the composition is the strongest form of associationは、the object(s) that compose or are contained by one object are destroyed too when that object is destroyedを意味します。
構成がどのように機能するかをよりよく理解するために、コンピューターを表すオブジェクトを操作する必要があると仮定しましょう.
コンピュータは、マイクロプロセッサ、メモリ、サウンドカードなどのさまざまな部分で構成されているため、コンピュータとその各部分の両方を個別のクラスとしてモデル化できます。
Computerクラスの単純な実装は次のようになります。
public class Computer {
private Processor processor;
private Memory memory;
private SoundCard soundCard;
// standard getters/setters/constructors
public Optional getSoundCard() {
return Optional.ofNullable(soundCard);
}
}
次のクラスは、マイクロプロセッサ、メモリ、およびサウンドカードをモデル化しています(簡潔にするために、インターフェイスは省略されています)。
public class StandardProcessor implements Processor {
private String model;
// standard getters/setters
}
public class StandardMemory implements Memory {
private String brand;
private String size;
// standard constructors, getters, toString
}
public class StandardSoundCard implements SoundCard {
private String brand;
// standard constructors, getters, toString
}
継承よりもコンポジションを推進する動機を理解するのは簡単です。 In every scenario where it’s possible to establish a semantically correct “has-a” relationship between a given class and others, the composition is the right choice to make.
上記の例では、Computerは、そのパーツをモデル化するクラスで「has-a」条件を満たす。
この場合、the containing Computer object has ownership of the contained objects if and only if the objects can’t be reused within another Computer object.可能であれば、所有権が暗示されていない構成ではなく、集計を使用することにも注意してください。
5. 抽象化のない構成
または、コンストラクターで宣言する代わりに、Computerクラスの依存関係をハードコーディングすることで、構成関係を定義することもできます。
public class Computer {
private StandardProcessor processor
= new StandardProcessor("Intel I3");
private StandardMemory memory
= new StandardMemory("Kingston", "1TB");
// additional fields / methods
}
もちろん、ProcessorとMemoryの特定の実装に強く依存するComputerを作成するため、これは厳密で緊密に結合された設計になります。
インターフェースとdependency injectionによって提供される抽象化レベルを利用することはありません。
インターフェイスに基づく初期設計では、疎結合設計が得られますが、これもテストが容易です。
6. 結論
この記事では、Javaでの継承と構成の基本を学び、2種類の関係(is-aとvs. “has-aâ)。
いつものように、このチュートリアルに示されているすべてのコードサンプルはover on GitHubで利用できます。