SOLIDの原則にあるように、スイッチ条件をクラスとインターフェースに変換して削除することをお勧めします。次のコードで実行したいと思います。
注:このコードは実際のコードではなく、自分の考えを入れただけです。
MessageModel message = getMessageFromAnAPI();
manageMessage(message);
...
void manageMessage(MessageModel message){
switch(message.typeId) {
case 1: justSave(message); break;
case 2: notifyAll(message); break;
case 3: notify(message); break;
}
}
次に、switchステートメントを削除します。だから私はそれのためにいくつかのクラスを作成し、ここでポリモーフィズムを実装しようとします:
interface Message{
void manageMessage(MessageModel message);
}
class StorableMessage implements Message{
@Override
public void manageMessage(MessageModel message) {
justSave(message);
}
}
class PublicMessage implements Message{
@Override
public void manageMessage(MessageModel message) {
notifyAll(message);
}
}
class PrivateMessage implements Message{
@Override
public void manageMessage(MessageModel message) {
notify(message);
}
}
次に、APIを呼び出してMessageModel
を取得します。
MessageModel message = getMessageFromAnAPI();
今私の問題はここにあります。モデルがあり、クラスを使用して管理したい。 SOLID例として、私はこのようなことをする必要があります:
PublicMessage message = new Message();
message.manageMessage(message);
しかし、どのタイプがこのメッセージに関連しているのかを知り、それからインスタンスを作成するにはどうすればよいですか(PublicMessage
またはStorableMessage
またはPrivateMessage
)?!スイッチブロックをもう一度ここに配置する必要がありますか?
この場合、ファクトリを使用してMessage
のインスタンスを取得できます。ファクトリはMessage
のすべてのインスタンスを持ち、MessageModelのtypeIdに基づいて適切なインスタンスを返します。
class MessageFactory {
private StorableMessage storableMessage;
private PrivateMessage privateMessage;
private PublicMessage publicMessage;
//You can either create the above using new operator or inject it using some Dependency injection framework.
public getMessage(MessageModel message) {
switch(message.typeId) {
case 1: return storableMessage;
case 2: return publicMessage;
case 3: return privateMessage
default: //Handle appropriately
}
}
}
呼び出しコードは次のようになります
MessageFactory messageFactory; //Injected
...
MessageModel messageModel = getMessageFromAnAPI();
Message message = messageFactory.getMessage(messageModel);
message.manageMessage(messageModel);
ご覧のように、これはswitch
を完全に取り除いたわけではありません(スイッチを使用すること自体は悪いことではないので、不要です)。 SOLIDが言おうとしていることは、SRP(単一の責任の原則)とOCP(オープンクローズの原則)に従って、コードをクリーンに保つことです。ここで意味するところは、コードに各typeId
を1か所で処理する実際の処理ロジック。
ファクトリーを使用して、作成ロジックを別の場所に移動し、実際の処理ロジックをそれぞれのクラスにすでに移動しました。
編集:繰り返しますが、私の答えはSOLID OPの側面に焦点を当てています。個別のハンドラークラス(Message
のインスタンスをOP)SRPを達成します。ハンドラクラスの1つが変更された場合、または新しいメッセージtypeId(message.typeId
)(つまり、新しいMessage
実装を追加します)、オリジナルを変更する必要がないため、OCPを実現できます。 (これらのそれぞれに簡単なコードが含まれていないと仮定して)。これらはすでにOPで行われています。
ここで私の答えの本当のポイントは、ファクトリを使用してMessage
を取得することです。アイデアは、メインアプリケーションコードをクリーンに保ち、スイッチ、if/else、および新しい演算子をインスタンス化コードに制限することです。 (@Configurationクラス/ GuiceでSpringまたはAbstractモジュールを使用するときにBeanをインスタンス化するクラスと同様)。 OO原則しないでくださいスイッチの使用は悪いと言います。それはに依存しますwhereあなたはそれを使用します。アプリケーションコードでそれを使用すると、SOLIDの原則に違反します。それが私が引き出したかったことです。
私はまた、daniu @のアイデアを機能的な方法で使用するのが好きです。同じ方法を上記のファクトリコードで使用することもできます(または単純なマップを使用してスイッチを取り除くこともできます)。
あなたはこれを行うことができます:
static final Map<Integer,Consumer<MessageModel>> handlers = new HashMap<>();
static {
handlers.put(1, m -> justSave(m));
handlers.put(2, m -> notifyAll(m));
handlers.put(3, m -> notify(m));
}
これにより、スイッチが削除されます
Consumer<Message> consumer = handlers.get(message.typeId);
if (consumer != null) { consumer.accept(message); }
もちろんこれをカプセル化する必要があります:
class MessageHandlingService implements Consumer<MessageModel> {
static final Map<Integer,Consumer<MessageModel>> handlers = new HashMap<>();
static {
handlers.put(1, m -> justSave(m));
handlers.put(2, m -> notifyAll(m));
handlers.put(3, m -> notify(m));
}
public void accept(MessageModel message) {
Consumer<Message> consumer = handlers.getOrDefault(message.typeId,
m -> throw new MessageNotSupportedException());
consumer.accept(message);
}
}
あなたのクライアントコードで
message = getMessageFromApi();
messageHandlingService.accept(message);
このサービスは「統合」部分です(「実装」とは対照的に、cfg統合操作の分離原則)。
CDIフレームワークを使用する本番環境の場合、これは次のようになります。
interface MessageHandler extends Consumer<MessageModel> {}
@Component
class MessageHandlingService implements MessageHandler {
Map<Integer,MessageHandler> handlers = new ConcurrentHashMap<>();
@Autowired
private SavingService saveService;
@Autowired
private NotificationService notificationService;
@PostConstruct
public void init() {
handlers.put(1, saveService::save);
handlers.put(2, notificationService::notifyAll);
handlers.put(3, notificationService::notify);
}
public void accept(MessageModel m) { // as above }
}
@ user7の答えのスイッチに対するこれの利点の1つは、動作を調整できることです実行時。あなたは次のような方法を想像することができます
public MessageHandler setMessageHandler(Integer id, MessageHandler newHandler);
これは与えられたMessageHandler
をインストールして古いものを返します;これにより、たとえば、デコレータを追加できます。
これが役立つ例は、処理を提供する信頼できないWebサービスがある場合です。アクセス可能な場合は、handlerrとしてインストールできます。それ以外の場合は、デフォルトのハンドラーが使用されます。
ここでの重要な点は、インスタンス化と構成を実行から分離することです。
OOPを使用しても、if/else
カスケードまたはswitch
ステートメントを使用して異なるケースを区別することは避けられません。結局、特殊な具象クラスのインスタンスを作成する必要があります。
しかし、これは初期化コードまたはある種のfactoryにあるはずです。
ビジネスロジック内で、_interfacesでジェネリックメソッドを呼び出して、if/else
カスケードまたはswitch
ステートメントを回避します。振る舞う。
通常のクリーンなコードアプローチは、MessageModelにその動作を含めることです。
_interface Message {
void manage();
}
abstract class MessageModel implements Message {
}
public class StoringMessage extends MessageModel {
public void manage() {
store();
}
}
public class NotifyingMessage extends MessageModel {
public void manage() {
notify();
}
}
_
次に、getMessageFromApi
は適切なタイプを返し、スイッチは
_MessageModel model = getMessageFromApi();
model.manage();
_
このように、生成するメッセージを決定する必要があるので、getMessageFromApi()
メソッドにスイッチがあります。
ただし、いずれにしてもメッセージタイプIDは満たされるため、問題ありません。また、クライアントコード(スイッチが現在存在する場所)はメッセージの変更に耐性があります。つまり、別のメッセージタイプの追加は正しく処理されます。
あなたが持っている本当の問題は、MessageModel
が多態性ではないということです。 MessageModel
sをポリモーフィックなMessage
クラスに変換する必要がありますが、このクラスのメッセージをどう処理するかのロジックを配置しないでください。代わりに、メッセージの実際のコンテンツが含まれている必要があり、 Eric's Answer に示すようにビジターパターンを使用して、他のクラスがMessage
を操作できるようにする必要があります。匿名を使用する必要はありませんVisitor
; MessageActionVisitor
のような実装クラスを作成できます。
MessageModel
sをさまざまなMessage
sに変換するには、 ser7の回答 に示すように、ファクトリを使用できます。返されるMessage
のタイプを選択することに加えて、ファクトリはMessage
を使用してMessageModel
の各タイプのフィールドに入力する必要があります。
Factory Pattern を使用できます。次の値を持つ列挙型を追加します。
public enum MessageFacotry{
STORING(StoringMessage.TYPE, StoringMessage.class),
PUBLIC_MESSAGE(PublicMessage.TYPE, PublicMessage.class),
PRIVATE_MESSAGE(PrivateMessage.TYPE, PrivateMessage.class);
Class<? extends Message> clazz;
int type;
private MessageFactory(int type, Class<? extends Message> clazz){
this.clazz = clazz;
this.type = type;
}
public static Message getMessageByType(int type){
for(MessageFactory mf : values()){
if(mf.type == type){
return mf.clazz.newInstance();
}
}
throw new ..
}
}
次に、その列挙型の静的メソッドを呼び出して、管理するメッセージのインスタンスを作成できます。