これは、Javaクラスの設計と、それがSpringとどのように相互作用するかについての一般的な質問ですが、例を使用して、少し話しやすくするようにします。(このコードはほとんどが疑似コードであり、多くの綿毛が除外されていることに注意してください)。
大きくて因数分解が不十分なクラスに遭遇したと想像してください。それは、再利用、テスト、または理解を困難にする多くのことを行っています。私の例では、「BulkyThingDoer」クラスがあります。このクラスは、あることを実行し、その実行回数と所要時間(JMXへの公開の場合)に関する統計も追跡します。ひどいことではありませんが、統計の数が増えるにつれて、クラスは簿記によって支配されるようになり、実際に重要なことはノイズの中で失われます。競合するDoerInterface実装がある場合は、同じ統計ツールなどを再現する必要があります。
@Component
@ManagedResource
public class BulkyThingDoer implements DoerInterface, DoerStatistics{
private int thingsDone = 0;
private long timeSpentDoingThings = 0;
public void doThing(){
long start = System.currentTimeMillis();
// do the important thing
thingsDone ++;
timeSpentDoingThings += System.currentTimeMillis() - start;
}
public int getThingsDone(){ return thingsDone;}
public long getTimeSpentDoingThings (){ return timeSpentDoingThings;}
}
ここで、このクラスをリファクタリングし、すべての統計簿記をヘルパーオブジェクトに分割し、実際の実行者に何が起こったかを(可能な限り最高レベルで)報告させることを想像してみましょう。これで、ThingDoerオブジェクトとDoerStatisticsオブジェクトはどちらも、シンプルでテスト可能な単一責任オブジェクトになりました。
@Component
public class ThingDoer implements DoerInterface {
private final DoerStatistics stats;
public ThingDoer(DoerStatistics stats){
this.stats = stats;
}
public void doThing(){
long start = System.currentTimeMillis() - start;
// do the important thing
stats.recordThatWeDidThing(System.currentTimeMillis() - start);
}
}
@ManagedResource
public class DoerStatisticHolder implements DoerStatistics{
private int thingsDone = 0;
private long timeSpentDoingThings = 0;
public void recordThatWeDidThing(long duration){
thingsDone ++;
timeSpentDoingThings += duration;
}
public int getThingsDone(){ return thingsDone;}
public long getTimeSpentDoingThings (){ return timeSpentDoingThings;}
}
私には、これが着手するのに良いリファクタリングになることは明らかです。これは、実装の詳細に至るまでDIの原則を拡張していると思います。
これはすべてこの問題の原因でした。このリファクタリングを実行すると、Springコンテキストファイルでクラスを使用するのがはるかに難しくなります。以前は単一の定義を使用でき、同じオブジェクトがBeanとして作成され、他のオブジェクトに与える(または自動配線する)ことができましたが、コンテキストで複数の部分を定義し、それらをすべて手動で配線する必要があります。これにより、必要以上に多くの実装の詳細がユーザーに公開されます。
1つの代替方法は、「BulkyThingDoer」ヘルパークラスを効果的に再構築して、適切な部分でオブジェクトを構築することです。
public class DelegatedBulkyThingDoer extends DoerInterface{
ThingDoer doer;
public DelegatedBulkyThingDoer(){
DoerStatistics stats = new DoerStatisticHolder();
doer= new ThingDoer(stats );
}
public void doThing(){
doer.doThing();
}
}
これでユーザビリティの問題の一部は解決しますが、DoerStatisticHolderとThingDoerのどちらでも春の魔法は得られません。この例では、JMXが登録されていないことを意味します。これらのサブオブジェクトが作成されていることをSpringに通知する簡単な方法があれば、それはおそらく私の問題の解決に役立つでしょう。
もちろん、元のBulky実装で行ったすべての呼び出しを委任し、2つの実装クラスのすべてのアノテーションを削除することもできますが、それは私には信じられないほどエレガントに聞こえます。私は効果的にBulyClassを再構築しましたが、これはJMXやロギングなどの横断的関心事にのみ必要だったため特に厄介です。
私は、元の実装が以前と同じくらい大きくて複雑になることにつながるその苦痛を主張します。Springは、このような問題のために、開発者に実装レベルでの厳密なOOP原則の適用をやめるように促しているようです。私がこのパターンを掘り下げたSpringifiedコードベースでは、何度も何度も登場します。クラスは、「高レベル」のSpringコンテキストの使用(単一のBean def)をサポートするために単一のクラスに実装され、さらに分解されることはありません。それが起こるとき、春の魔法を失います。
私は春とJava(ここ数年はc ++、scala、rubyでした)に比較的慣れていないので、何かが足りないかもしれません。Java / Spring開発者は、Springのユーザビリティと十分に考慮された実装の間のこの「インピーダンスの不一致」をどのように処理しますか?彼らは弾丸を噛んでDelegatingBulkyClassを作成しますか、ユーザーにリファクタリングされたimplクラスを構築するように強制しますか、それとも最も簡単なのでBulkyClassパターンに固執しますか?