【しばらく編集不可モードで運営します】 編集(管理者用) | 差分 | 新規作成 | 一覧 | RSS | FrontPage | 検索 | 更新履歴

メソッドへの引数による処理内容の制御 - フラグを用いて、処理を分岐する

目次

フラグを用いて、処理を分岐する

フラグを用いて処理の内容を分岐すると、どのような影響を及ぼすか(問題提起)

「…するかどうか」をメソッドのパラメータとして、処理の内容を分岐する。

例)

	
 function ReverseString(Target: string; IsIgnoreNumeric: Boolean); string;

 ReverseString('ab12c', true); とすると 'cba' が返される。

 ReverseString('ab12c', false); とすると 'c21ba' が返される。

 つまり、パラメータ IsIgnoreNumeric に応じて、処理内容を分岐している。

一般論として

「フラグによって動作を変えるようなルーチン、及びそのようなメソッドを設計する」ことは、 一般的には、あまり良い事ではない。

フラグを使用せず、処理を分岐するには

ある条件によって処理を分岐しなければならないとき、 メソッドのパラメータには、その情報を持たせたくない。

そのようなメソッドが、もし見つかった場合の対処法は?(リファクタリング)

これまでの考察

 1) 個々の処理を一つのメソッドでラッピングする

 String Drop_Numbers(String str);   // 文字列から数字を取り除く
 String Reverse_String(String str); // 文字列をひっくり返す

 String Drop_Numbers_And_Reverse_String(String str) {
   return Reverse_String(Drop_Nnumbers(str));
 }

じつは

Drop_Numbers_And_Reverse_String(); なんて関数はいりません。 Reverse_String('ab12c', true); と書いてあるところを Reverse_String(Drop_Numbers('ab12c')); とするだけなんです。

Reverse_String()が数字のほかに記号類も読み飛ばしたいという変更が入ったとき、 フラグを渡す方式をとっていたらReverse_String()自体をいじらなくてはならないのですが、 関数を別に作って組み合わせる方式なら、記号類を読み飛ばす関数を別途追加するだけで 済みますよね。

そしてもしReverse_String()をいじってしまったら、他のReverse_String()を使っているコードに 影響が出ないか確認して回らなければならないですし、関数の行数もフラグの追加によって 増加する一方ですから保守性は下がると思います。

ここでは、Reverse_String()という例に基づいて一般論を言っていますが、 実務的なコードを書いていて、フラグによる制御がベストな選択になる という状況も(想像はつきませんが)あり得ると認識しています。

参考資料

凝集度と結合度ついて
http://village.infoweb.ne.jp/~fwgf2942/KeyWords/KW.Coupling.html

参考資料との関連

フラグによって処理を分岐しなければならないというのは、 処理A-B-Cという仕事の流れと処理A-B-Dという仕事の流れがあって、 これらが「処理A-B-CないしD」という一つのメソッドにまとめられている 状態なのではないでしょうか?

凝集度と結合度の定義を見ると、 「処理A-B-CないしD」は“レベル2:論理的凝集”なのかも知れません。 そして、このメソッドとそれを呼び出すメソッドとの関係は、“レベル3:制御結合” なのでは。

サンプルプログラム (実際に、修正していく過程を見る)

 

サンプルプログラム (1)

 firstMethod → secondMethod → thirdMethod1 という処理過程と
 firstMethod → secondMethod → thirdMethod2 という処理過程がある場合、

 public class Main {
     public static void main(String[] args) {
         SimpleClass simpleClass;
         simpleClass = new SimpleClass();
         simpleClass.process(true);
         simpleClass.process(false); 
     }
 }
 
 class SimpleClass {
     public void firstMethod() {
         System.out.println("firstMethod");
     }
     public void secondMethod() {
         System.out.println("secondMethod");
     }
     public void thirdMethod1() {
         System.out.println("thirdMethod1");
     }
     public void thirdMethod2() {
         System.out.println("thirdMethod2");
     }
     public void process(boolean someFlg) {
         if (someFlg) {
             firstMethod();
             secondMethod();
             thirdMethod1();
         } else {
             firstMethod();
             secondMethod();
             thirdMethod2();
         }
     }
 }

 フラグ someFlg をなくしたい、どうすればよいか?

サンプル(1) → 無名クラスを使う

 public class Main {
   public static void main(String[] args) {
     SimpleClass simpleClass;
     simpleClass = new SimpleClass();
     simpleClass.process(
       new Functor() {
         public void compute() {
           System.out.println("thirdMethod ... 1");
         }
       });
     simpleClass.process(
       new Functor() {
         public void compute() {
           System.out.println("thirdMethod ... 2");
         }
       });
   }
 }
 
 class SimpleClass {
   public void firstMethod() {
     System.out.println("firstMethod");
   }
   public void secondMethod() {
     System.out.println("secondMethod");
   }
   public void thirdMethod(Functor fn) {
     fn.compute();
   }
   public void process(Functor fn) {
     firstMethod();
     secondMethod();
     thirdMethod(fn);
   }
 }
 
 class Functor {
 
 }

※Javaはよく知らないので、文法が間違っている箇所は(断りを入れず)に訂正しちゃってください。

そのScheme版

 (define (main)
   (simple
     (lambda () (display "third-method ... 1")
                (newline)))
   (simple
     (lambda () (display "third-method ... 2")
                (newline))))
 
 (define (simple fn)
   (define (first-method)
     (display "first-method")
     (newline))
   (define (second-method)
     (display "second-method")
     (newline))
   (define (third-method fn)
     (fn))
   (begin
     (first-method)
     (second-method)
     (third-method fn)))

サンプルプログラム (2)

 open → print → close                 という処理過程と
 open → print → specialPrint → close という処理過程がある

 public class Main {
     public static void main(String[] args) {
         SimpleClass simpleClass;
         simpleClass = new SimpleClass();
         simpleClass.process(true);
         simpleClass.process(false);
     }
 }

 class SimpleClass {
     public void open() {
         System.out.println("open");
     }
     public void print() {
         System.out.println("print");
     }
     public void specialPrint() {
         System.out.println("specialPrint");
     }
     public void close() {
         System.out.println("close");
     }
     public void process(boolean someFlg) {
         if (someFlg) {
             open();
             print();
             specialPrint();
             close();
         } else {
             open();
             print();
             close();
         }
     }
 }

 フラグ someFlg をなくしたい、どうすれば良いか?

 シンプルなサンプルですが、ぜひ突っ込み / 修正(リファクタリング)
 など、いただけたらと思います。

「メソッドXを受け取って、処理A-B-Xを行う」というメソッドにしておいて、呼び出す時にメソッドCまたはDを一緒に渡す

 type
   TSomeProc = procedure;
   TReceiveXProc = procedure(SomeProc: TSomeProc);

 procedure A;
 begin
   Writeln('A');
 end;

 procedure B;
 begin
   Writeln('B');
 end;

 procedure MethodC;
 begin
   Writeln('MethodC');
 end;

 procedure MethodD;
 begin
   Writeln('MethodD');
 end;

 procedure SubProcedure(SomeProc: TSomeProc);
 begin
   SomeProc;
 end;

 procedure BaseProcedure(ReceiveX: TReceiveXProc; SomeProc: TSomeProc);
 begin
   A;
   B;
   ReceiveX(SomeProc);
 end;

 var
   SomeProc: TSomeProc;
   ReceiveXProc: TReceiveXProc;
 begin
   SomeProc := MethodC;
   ReceiveXProc := SubProcedure;
   BaseProcedure(ReceiveXProc, SomeProc);

   Readln;
 end.
 
 -- 実行結果 --
 A
 B
 MethodC

 突っ込み、修正歓迎です。

perl での例

コード

 #!/usr/bin/perl

 sub a_b_x {
   my $x = shift;
   print "process a\n";
   print "process b\n";
   &$x;
 }

 print "Start process\n";
 print "case c\n";
 a_b_x sub { print "process c\n" };

 print "case d\n";
 a_b_x sub { print "process d\n" };

 __END__

実行結果

 $ perl abx.pl
 Start process
 case c
 process a
 process b
 process c
 case d
 process a
 process b
 process d

perlの反則oo

 #!/usr/bin/perl
 
 package abx;
 sub new {
   my $class_self = shift;
   my $class_dist = shift;
   return $class_dist->new;
 }
 
 package abc;
 sub new {
   my $self = {};
   return bless $self, shift;
 }
 
 sub process {
   my $self = shift;
   $self->a;
   $self->b;
   $self->x;
 }

 sub a { print "process a\n" }
 sub b { print "process b\n" }
 sub x { print "process c\n" }

 package abd;
 @ISA = qw(abc);
 sub x { print "process d\n" }

 package main;
 print "Start process\n";
 print "case c\n";
 abx->new(abc)->process;
 print "case d\n";
 abx->new(abd)->process;

適用できそうなデザインパターン

Template method パターン
http://www.dmz.hitachi-sk.co.jp/Java/Tech/pattern/gof/templatemethod.html
Strategy パターン
http://www.dmz.hitachi-sk.co.jp/Java/Tech/pattern/gof/strategy.html

コメント

YukiWikiはコメント欄を一ページにつき一個しか設置できないようです。