「インターフェースへのプログラミング」を理解する によると、私が理解しているように、私は抽象クラスのみに依存するべきだと思います。ただし、場合によっては、たとえば、Student
:
public class Student {
private String name;
private int age;
}
抽象クラスのみに依存するように変更します(これはMyIString
がString
をラップする新しい抽象クラスである場合があります):
public class Student {
private MyIString name;
private Java.lang.Number age;
}
修正した方が複雑だと思います。さらに「実際の」例として、Address
と言います。
public class Address {
private ZipCode zipcode;
}
ZipCode
の1つのタイプのみが必要ですが、次のように変更すると、
public class Address {
private IZipCode zipcode;
}
どのIZipCode
がインターフェイスであるかについては、他のタイプのZipCodes
があると他のチームメートに誤解を与える可能性があると思います。
クラスが抽象クラスメンバーのみを使用することを許可された場合、上記のケースはより複雑になり、保守性が低下すると思います。だから私の質問は、「フォローされている」方が(私の見方では)より複雑になった場合でも、「実装ではなくインターフェースへのプログラミング」に従うべきですか?
インターフェイスへのプログラミングは、実際に実装されているhowではなく、-whatコードに焦点を当てるべきであることを意味します。 「インターフェイスへのプログラミング」を理解するためのTelastynの回答 を参照してください。インターフェイスクラスはこのガイドラインを適用するのに役立ちますが、これは具体的なクラスを使用してはならないという意味ではありません。
私は郵便番号の例が本当に好きです:
1963年、米国郵政公社は5桁の郵便番号を導入しました。整数は十分な表現であり、コード全体で使用できるという(悪い)考えが得られるでしょう。 1983年、新しい郵便番号形式が導入されました。 5桁、ハイフン、およびその他の4桁を使用します。突然、郵便番号は整数ではなくなり、郵便番号に整数を使用しているすべての場所を別の文字列などに変更する必要があります。指定されたZipCode
クラスが使用された場合、このリファクタリングは回避できたはずです。次に、ZipCode
クラスの内部表現のみを変更する必要があり、その使用法は同じままでした。
ZipCode
クラスの使用はすでにインターフェイスへのプログラミング:いくつかの具体的な実装( "整数"/"文字列")に対してプログラミングする代わりに、いくつかの抽象化( "郵便番号")に対してプログラミングします)。
必要に応じて、別のレベルの抽象化を追加できます。国ごとに異なる郵便番号形式をサポートする必要がある場合があります。たとえば、IPostalCode
(上記のPostalCodeUS
のみ)、ZipCode
、PostalCodeIreland
などの実装を持つインターフェイスPostalCodeNetherlands
を追加できます。ただし、抽象化のレベルが多すぎると、コードがより複雑になり、推論が難しくなるため、 ZipCode
クラスを使用するかIPostalCode
インターフェイスを使用するかは、アプリケーションの要件によって異なります。多分、(unicode)文字列を内部で使用するZipCode
は、すべての国に十分であり、たとえば、検証は、一部のPostalCodeValidator
/IPostalCodeValidator
のクラス外で処理できます。
「インターフェイスへのプログラミング」では、言語キーワードinterface
は必要ありません。それは、型がその振る舞いに関して提供する約束を気にかけることを意味します。
あなたは気にしない方法Java.lang.String
は、あなたが書くことができることだけを行います。
name = "aacceeggiikk";
age = 42;
「実装へのプログラミング」は、リフレクションを使用してString
のプライベートメンバーに到達し、それらをいじくります。そして、変更された何かに依存していたために、更新がコードを壊したことに腹を立てています。
これは単なるガイドラインです
[〜#〜] kiss [〜#〜] のような概念がガイドラインです。
「実装ではなくインターフェイスへのプログラム」のガイドラインに従うと、KISS原則に違反します。ただし、いくつかのコードまたはアクションを繰り返す一連の単純なクラスを作成することにより、 [〜#〜] dry [〜#〜] の原則に違反することになります。
そこにあるすべてのガイドライン、原則、または「ルール」を守る方法はありません。 yourアプリケーションにとってどのようなバランスが理にかなっているのかを知る必要があります。
私自身の経験では、抽象化されたレイヤーの上に抽象化されたレイヤーを使用してすべての企業を証明することは、やり過ぎであり、多くのアプリケーションにとって不必要に複雑であるということです。
インターフェイスへのプログラミングは、オブジェクトに関係します。オブジェクトには、名前を付けるためにboolean、int、BigDecimal、Stringなどのプリミティブ型のプロパティを持つこともできます。そのレベルでは、インターフェースへのプログラミングは適用されません。
ただし、より抽象的な値クラスへのラッパーがあるはずのプリミティブ型が使用されているという経験に基づく、他のプリンシパルがあります。代わりに
_String telephone;
_
あなたは値クラスの電話を持つ方が良いかもしれません電話:
_Telephone telephone.
_
これにより、国番号、正規形、平等などを処理できます。繰り返しになりますが、Telephoneはインターフェイスであり、実装クラスTelephoneImplが存在する可能性がありますが、私は(幸いにも)これをめったに見ません。
さらに良いのは、複数のプリミティブフィールドをFullName
のようなより抽象的なクラス、たとえばint similarityPercentage(FullName other)
で置き換えることができる場合です。
たぶん、内部実装を持つ内部オブジェクトがインターフェースを利用できることは言及されるべきです。 List
には、リストコンテナーにアクセスできるローカルオブジェクトを実装するインターフェイスIterator iterator()
があります。ただし、ここでのインターフェースはソリューションの一部であり、コーディングスタイルではありません。
簡潔に:
プログラミングによるインターフェイスは実装クラスを分離し、複数の実装を許可し、APIとして単一の責任を持ちます。それは振る舞い、方法に関係しています。 JDBCを例にとります。
フィールドは具体的なプロパティです。それらはdataです。それらは抽象化され、正しい使用法のためにクラス名を使用して実装を隠し、アクセスメソッドを提供する必要もあります。 しかし、それは別のレベルです。すべての古い日付の代わりにクラスLocalDate、LocalDateTime、YearMonthを取得します。
私の同僚を引用するには-"コンピュータで車を設計するのに何時間も費やすことができますが、ある時点で、車輪は実際に地面に触れる必要があります。」.
つまり、すべてを抽象化できるわけではなく、抽象化する必要があるわけではありません。ある時点で、具体的な型が必要になります。
あなたの郵便番号を使用するクラスの場合、質問は本当にis郵便番号(アプリケーションの観点から)である必要がありますか?
Answer-これは文字列なので、プロパティは文字列である必要があります。
public class Address {
public String ZipCode...
}
追伸基礎となる言語タイプを抽象化することから始めないでください-この方法は狂気です!
さまざまな国のさまざまな郵便番号にコメントする多くの回答/コメントがありますが、それは事実ですが、最終的にはすべて文字列です。
うまくいけば、このAddressクラスは郵便番号の内容を気にせず(文字列であることだけ)、Addressの検証に使用する別のクラスがあります(そしてあれクラスが役に立つかもしれません)。
そのため、そうすることが理にかなっているインターフェース、特に実装を変更したり、動作を注入したりする必要があるインターフェースにプログラムします。すべてにインターフェースを使用しないでください!
あなたのアドレス/郵便番号の例では:
public class Address {
private ZipCode zipcode;
}
インターフェースではなく具体的な実装に縛られると、3つの問題が生じます。
public class Address : IAddress
{
public IZipCode ZipCode{get; private set;}
public string StreetName{get;private set}
}
次のようなものに:
public class Address : IAddress
{
public Address(IGeoCodeService geoCodeService/*Dependency injected*/){........}
private long What3WordsGeoCodeAsLong {get; set;}
public IZipCode ZipCode => GetZipCodeFromGeoCode();//calls out to a service to
public string StreetName => GetStreetNameFromGeoCode();
}
シリアル化された場合、2番目の実装はmuch格納するのに小さくなり、一部の国が郵便番号エリアを移動することを決定した場合に古くならないようにします。毎回郵便番号を検索するためにサービスを使用する必要があります。)一方、IAddressを使用するものは、実装が完全に異なることに「気づく」ことさえありません。
これは「インターフェイスへのプログラミング」に関する私の見解です。これには、入力と出力の2つの要素があります。
dataのオブジェクトとbehaviorのオブジェクト、特に副作用のオブジェクトを区別します。
どちらの例でも、具体的なクラスを使用します。文字列と郵便番号はデータ、IMOです。 (答えの1つが示唆するように、私はZipCodeを文字列にするだけではありません!!!古い文字列を渡してZipCodeのように扱うことはできません。ZipCodeクラスには、さまざまな種類のZipコードに対して異なるコンストラクターが必要ですInterally文字列を保存するかもしれませんが、少なくともコンストラクタで検証を行います!)。
ただし、何らかの "計算"を行うオブジェクト(あいまいな用語です)が必要な場合は、インターフェイスを使用します。たとえば、名前で生徒を検索できるオブジェクトが必要な場合は、入力用のインターフェースを使用します。インターフェイスは次のようになります。
_interface StudentSearchInterface {
Optional<Student> search(String query);
}
_
これが良いアイデアである理由は、後でプロバイダーを交換できることと、特定のテストに必要なものを正確に返す「偽の」StudentSearchInterfaceオブジェクトをコード化できるはるかに優れたテストを作成できることです。 StudentSearchInterfaceを実装する本番クラスをテストするのは難しいかもしれません。インターネット上のサーバーにクエリを実行するとどうなるでしょうか。またはデータベースから読み取りますか? search()
を呼び出すたびに結果が変わる可能性があるため、実際にテストすることはできません。
戻り値も「データ」またはインターフェイスのいずれかである必要があります。データとは、実際にはゲッターやセッター、あるいはtoString()
などのいくつかの簡単なメソッドのみを含むクラスを意味します。それ以外の場合は、インターフェースを実行します。一般的な例は、_List<Foo>
_ではなく_ArrayList<Foo>
_を返すことです。これにより、特定のList
インプリメンターでスタックするのではなく、List
を実装する別のものを使用するようにメソッド内のロジックを自由に変更できるようになります。
一部のメソッドとフィールドをprivate
としてマークしてそれらをカプセル化するのと同じように、インターフェイスを使用して、「知る必要がある」という基準でのみ情報を提供する必要があります。