IRCメッセージを受信するボットを実装していて、そのメッセージをチェックして、呼び出す関数を決定しています。これを行うためのより賢い方法はありますか?私が20個ほどのコマンドを取得した後、すぐに手に負えなくなりました。
おそらくこれを抽象化するより良い方法がありますか?
public void onMessage(String channel, String sender, String login, String hostname, String message){
if (message.equalsIgnoreCase(".np")){
// TODO: Use Last.fm API to find the now playing
} else if (message.toLowerCase().startsWith(".register")) {
cmd.registerLastNick(channel, sender, message);
} else if (message.toLowerCase().startsWith("give us a countdown")) {
cmd.countdown(channel, message);
} else if (message.toLowerCase().startsWith("remember am routine")) {
cmd.updateAmRoutine(channel, message, sender);
}
}
ディスパッチテーブル を使用します。これはペア(「メッセージ部分」、pointer-to-function
)を含むテーブルです。ディスパッチャーは次のようになります(疑似コード):
for each (row in dispatchTable)
{
if(message.toLowerCase().startsWith(row.messagePart))
{
row.theFunction(message);
break;
}
}
(equalsIgnoreCase
は、どこかで特別なケースとして処理できます。または、これらのテストの多くがある場合は、2番目のディスパッチテーブルを使用できます)。
もちろん、pointer-to-function
がどのように見えるかは、プログラミング言語によって異なります。 ここ は、CまたはC++の例です。 JavaまたはC#では、おそらくそのためにラムダ式を使用するか、コマンドパターンを使用して「関数へのポインタ」をシミュレートします。無料のオンラインブック " Higher Order Perl "には、Perlを使用したディスパッチテーブルに関する完全な章があります。
私はおそらくこのようなことをするでしょう:
public interface Command {
boolean matches(String message);
void execute(String channel, String sender, String login,
String hostname, String message);
}
次に、すべてのコマンドでこのインターフェイスを実装し、メッセージと一致した場合にtrueを返すことができます。
List<Command> activeCommands = new ArrayList<>();
activeCommands.add(new LastFMCommand());
activeCommands.add(new RegisterLastNickCommand());
// etc.
for (Command command : activeCommands) {
if (command.matches(message)) {
command.execute(channel, sender, login, hostname, message);
break; // handle the first matching command only
}
}
Java-だから美しくする;-)
私はおそらく注釈を使用してこれを行うでしょう:
カスタムメソッドアノテーションを作成する
_@IRCCommand( String command, boolean perfectmatch = false )
_
クラス内の関連するすべてのメソッドに注釈を追加します。
_@IRCCommand( command = ".np", perfectmatch = true )
doNP( ... )
_
コンストラクターでReflectionsを使用して、クラス内のすべての注釈付きメソッドからメソッドのHashMapを作成します。
_...
for (Method m : getDeclaredMethods()) {
if ( isAnnotationPresent... ) {
commandList.put(m.getAnnotation(...), m);
...
_
onMessage
メソッドで、commandList
に対してループを実行して、それぞれの文字列を照合し、適切な場所でmethod.invoke()
を呼び出します。
_for ( @IRCCommand a : commanMap.keyList() ) {
if ( cmd.equalsIgnoreCase( a.command )
|| ( cmd.startsWith( a.command ) && !a.perfectMatch ) {
commandMap.get( a ).invoke( this, cmd );
_
インターフェースを定義した場合、IChatBehaviour
と呼ばれる1つのメソッドがあり、Execute
とmessage
オブジェクトを取り込むcmd
としましょう。
public Interface IChatBehaviour
{
public void execute(String message, CMD cmd);
}
次に、コードでこのインターフェイスを実装し、必要な動作を定義します。
public class RegisterLastNick implements IChatBehaviour
{
public void execute(String message, CMD cmd)
{
if (message.toLowerCase().startsWith(".register"))
{
cmd.registerLastNick(channel, sender, message);
}
}
}
以降も同様です。
メインクラスには、behaviours(List<IChatBehaviour>
)IRC botが実装します。次に、if
ステートメントを次のように置き換えることができます。
for(IChatBehaviour behaviour : this.behaviours)
{
behaviour.execute(message, cmd);
}
上記はあなたが持っているコードの量を減らすはずです。上記のアプローチでは、ボットクラス自体を変更することなく、ボットクラスに追加の動作を提供することもできます(- Strategy Design Pattern
)。
一度に1つのビヘイビアーだけを起動する場合は、execute
メソッドのシグネチャを変更して、true
(ビヘイビアーが起動した)またはfalse
(動作は発生しませんでした)、上記のループを次のようなものに置き換えます。
for(IChatBehaviour behaviour : this.behaviours)
{
if(behaviour.execute(message, cmd))
{
break;
}
}
上記は、すべての追加クラスを作成して渡す必要があるため、実装と初期化が面倒ですが、すべての動作クラスがカプセル化され、うまくいけば互いに独立しています。
「インテリジェント」は、(少なくとも)3つのことです。
ディスパッチテーブル(およびそれに相当するもの)の提案は適切です。このようなテーブルは、「追加できない、試しさえしない」ということで「CADET」と呼ばれていました。ただし、コメントを検討して、初心者のメンテナーが上記のテーブルを管理する方法だけを支援するようにしてください。
「美しくする」は怠惰な警告ではありません。
そして、しばしば見落とされています...
ToLowerCaseの使用には、一部の言語の一部のテキストで、magisculeとminisculeを切り替えるときに苦痛な再構成を行わなければならないという落とし穴があります。残念ながら、toUpperCaseにも同じ落とし穴があります。注意してください。
すべてのコマンドに同じインターフェースを実装させることができます。次に、メッセージパーサーは、実行するだけの適切なコマンドを返します。
public interface Command {
public void execute(String channel, String message, String sender) throws Exception;
}
public class MessageParser {
public Command parseCommandFromMessage(String message) {
// TODO Put your if/switch or something more clever here
// e.g. return new CountdownCommand();
}
}
public class Whatever {
public void onMessage(String channel, String sender, String login, String hostname, String message) {
Command c = new MessageParser().parseCommandFromMessage(message);
c.execute(channel, message, sender);
}
}
それはちょうどより多くのコードのように見えます。はい、実行するコマンドを知るためにメッセージを解析する必要がありますが、今は適切に定義されたポイントにあります。他の場所で再利用できます。 (MessageParserを挿入することもできますが、それは別の問題です。また、作成するコマンドの数によっては、 Flyweight pattern がコマンドに適している場合があります。)
私がすることはこれです:
これにより、管理が容易になります。 「else if」の数が増えすぎると、より多くのメリットがあります。
もちろん、「もしそうなら」これらを持っていることは大きな問題ではないでしょう。 20はそんなに悪いとは思わない。