私はクラスに2つのコンストラクターがあり、非常によく似たコードがある状況にあります。唯一の違いは、スーパークラスのコンストラクターの呼び出しです。この共通コードはどこに置くべきですか?インスタンスブロックを使用しようとしましたが、インスタンスブロックでは必要な引数を渡すことができません。
また、フィールドは最終です。したがって、コンストラクタ以外の初期化はできません。
私のコードは次のようになります:
private final SourceCache sourceCache;
private final ServiceCache serviceCache;
private final MethodCache methodCache;
private final ModelCache modelCache;
private final QueryFactory queryFactory;
public MetaDataPersistenceHandler(
final Transaction transaction)
{
super(transaction);
this.transaction = transaction;
this.sourceCache = new SourceCache(transaction);
this.serviceCache = new ServiceCache(transaction);
this.methodCache = new MethodCache(transaction);
this.modelCache = new ModelCache(transaction);
this.queryFactory = new QueryFactory();
this.transaction.addQueryFactory(this.queryFactory);
}
public MetaDataPersistenceHandler(
final Transaction transaction,
final long fileSize)
{
super(transaction, fileSize);
this.transaction = transaction;
this.sourceCache = new SourceCache(transaction);
this.serviceCache = new ServiceCache(transaction);
this.methodCache = new MethodCache(transaction);
this.modelCache = new ModelCache(transaction);
this.queryFactory = new QueryFactory();
this.transaction.addQueryFactory(this.queryFactory);
}
スーパークラスには、fileSize
パラメータのデフォルト値があります(ほとんどの場合)。ここでその事実を利用できます。
まず、そのデフォルト値が何であるかを理解する必要があります。次のようなものが表示されます。
public class Handler {
public static final long DEFAULT_FILE_SIZE = 4096;
private final long fileSize;
public Handler(Transaction tr) {
this(tr, DEFAULT_FILE_SIZE)
}
public Handler(Transaction tr, long fsize) {
this.fileSize = fsize;
// ...
}
}
これにより、特にその定数がサブクラスに可視である場合に特に簡単になります。
public MetaDataPersistenceHandler(Transaction tr) {
this(tr, Handler.DEFAULT_FILE_SIZE);
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
super(tr, fileSize);
// etc...
}
または、定数はないが単純な値がある場合は、スーパークラスの定数に抽出して参照できます。
デフォルト値の周りのロジックがもう少し複雑な場合は、代わりにスーパークラスで可視の静的メソッドを抽出できます。
protected static long defaultFileSize(Transaction tr) {
if (tr.isHuge()) {
return 4096;
} else {
return 128;
}
}
同様の方法でサブクラスコンストラクターから呼び出します。
public MetaDataPersistenceHandler(Transaction tr) {
this(tr, Handler.defaultFileSize(tr));
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
super(tr, fileSize);
// etc...
}
2つのクラスに対するこれらの変更には、公開されたパブリックインターフェイスを保持するという利点があります。どのコンストラクターのシグニチャーも変更する必要はありません。そして、クラスメンバーを希望どおりに最終的に保つことができます。
スーパークラスを変更できないが、デフォルト値が変更される可能性が低い場合は、デフォルト値を定数または静的メソッドとしてサブクラスに常に入れ、スーパークラスとの重複を示す大きなコメントを付けることができます。
それはいいことですが、スーパークラスは実際にはスーパーコンストラクタ間で2つのまったく異なることを行います。痛い、それは本当に良いデザインではありませんが、OK、それでもsomethingを実行できます。これは、スーパークラスを変更できないために複製を望まない場合にも当てはまります。
継承の代わりに、いくつかのcompositionを実行できます。
これは、スーパークラスの上に本当に必要なもののためのスーパーインターフェースがある場合に最適に機能しますが、あまりにも敵対的でない場合は、スーパークラスだけでそれを行う方法があります。 Handler
インターフェースとSimpleHandler
スーパークラスがあるとします。クラスはHandler
を拡張する代わりにSimpleHandler
を実装でき、コンストラクターを変更してHandler
インスタンスを取得できます。
public class MetaDataPersistenceHandler implements Handler {
private final Handler handler;
// other memebers...
public MetaDataPersistenceHandler(Handler handler) {
this.handler = handler;
// etc...
}
}
元のコンストラクター呼び出しの代わりに、次のようにします。
new MetaDataPersistenceHandler(new SimpleHandler(transaction));
new MetaDataPersistenceHandler(new SimpleHandler(transaction, fileSize));
ここでの問題は、Handler
のすべてのメソッドを自分で実装する必要があることです。これを行うには、Handler
インターフェースのすべてのメソッドを作成し、それらをメンバーHandler
インスタンスに委任します。
@Override
public void handle() {
this.handler.handle();
}
残念ながら、これは定型のように見えますが、委任を行う最も簡単な方法です。ただし、そのためには、スーパークラスを変更したり、何らかの方法でデフォルトのファイルサイズの変更を処理したりすることを心配する必要はありません。サブクラスの外部インターフェースは、コンストラクターのシグニチャーを変更する必要があるため変更されました。
この方法を使いたいが、実際にこれらのコンストラクターの署名を変更できない場合は、それを回避する方法さえあります。
public class MetaDataPersistenceHandler implements Handler {
private final Handler handler;
// other memebers...
private MetaDataPersistenceHandler(Handler handler) {
this.handler = handler;
// etc...
}
public MetaDataPersistenceHandler(Transaction tr) {
this(new SimpleHandler(tr));
}
public MetaDataPersistenceHandler(Transaction tr, long fileSize) {
this(new SimpleHandler(tr, fileSize));
}
// etc...
}
たぶん私はこれを正しくしていません。 Handler
は抽象的な「戦略」タイプのクラスです。オーバーライドできるいくつかの抽象メソッドと、できない一連の最終メソッドがあります。 まだこれで作業できると思います。構成関係を反転するだけです。
まず、Handler
のすべての抽象メソッドのインターフェースを作成しましょう。
public interface BasicHandler {
void handle();
}
次に、抽象メソッドを実装するシンプルなハンドラーを作成し、コンストラクターを介して取得するBasicHandler
に委譲します。
public class DelegatingHandler extends Handler {
private final BasicHandler handler;
public DelegatingHandler(Transaction tr, BasicHandler handler) {
super(tr);
this.handler = handler;
}
public DelegatingHandler(Transaction tr, long fileSize, BasicHandler handler) {
super(tr, fileSize);
this.handler = handler;
}
@Override
public void handle() {
this.handler.handle();
}
}
そして、BasicHandler
の独自の実装を書くことができます:
public class MetaDataPersistenceBasicHandler implements BasicHandler {
// members...
public MetaDataPersistenceBasicHandler(Transaction tr) {
// Everything that goes here...
}
@Override
public void handle() {
// our special way to handle...
}
}
これで、次のようなHandler
sを作成できます。
new DelegatingHandler(tr, new MetaDataPersistenceBasicHandler(tr));
new DelegatingHandler(tr, fileSize, new MetaDataPersistenceBasicHandler(tr));
ここでも、コンストラクタのシグネチャを変更しました。
重複するコードの他のリファクタリングと同じように、重複するコードを新しいプライベートメソッドに配置します。
private SourceCache sourceCache;
private ServiceCache serviceCache;
private MethodCache methodCache;
private ModelCache modelCache;
private QueryFactory queryFactory;
public MetaDataPersistenceHandler(final Transaction transaction)
{
super(transaction);
AddCachesAndFactoriesTo(transaction);
}
public MetaDataPersistenceHandler(final Transaction transaction, final long fileSize)
{
super(transaction, fileSize);
AddCachesAndFactoriesTo(transaction);
}
private AddCachesAndFactoriesTo(Transaction transaction)
{
this.transaction = transaction;
this.sourceCache = new SourceCache(transaction);
this.serviceCache = new ServiceCache(transaction);
this.methodCache = new MethodCache(transaction);
this.modelCache = new ModelCache(transaction);
this.queryFactory = new QueryFactory();
this.transaction.addQueryFactory(this.queryFactory);
}
ただし、プライベート変数のfinal
修飾子をあきらめる必要があります。