僕の生活は、土日は
朝起きて、 朝ごはんを食べて、 息子と遊んで、 お昼ごはんを食べて、 息子と遊んで、 夕飯を食べて 寝る
という素敵な生活なのですが、
平日は、
朝起きて、 糞みたいな仕事をして、 お昼ごはんを食べて、 糞みたいな仕事をして、 夕飯を食べて、 寝る
という糞みたいな生活です。
プログラムで書くと、
平日は
void weekday(){
getUp();
haveBreakFast();
playWithSon();
haveLunch();
playWithSon();
haveDinner();
fellAsleep();
}
休日は
void holiday(){
getUp();
haveBreakFast();
doFuckingWork();
haveLunch();
doFuckingWork();
haveDinner();
fellAsleep();
}
となってしまうんですが、これは僕の平日より糞すぎてゲロ吐いちゃいそうなコードです。
"起きて、
朝ごはん食べて、
なんかして、
昼ごはん食べて、
なんかして、
晩ごはん食べて、
ねる
"
っていうパターンは共通しています。
DRY則にのっとって、同じことを何度も書かずに済ませたいです。
そうでないと、あとから、
「おまえおきたらうんこたれるだろうがこのうんこたれ!」
といわれたとき、weekday()とholiday()の両方を直さなきゃならなくなるからです。
このとき、template method pattern的な方法で"なんかして"の部分を切り替えることが出来ます
すなわち、
class Day{ public: void doTasks(); private: void getUp(); void haveBreakFast(); void haveLuch(); void haveDinner(); void fellAsleep(); virtual void doSomeThing() = 0; }; void Day::doTasks(){ getUp(); haveBreakFast(); doSomeThing(); haveLunch(); doSomeThing(); haveDinner(); fellAsleep(); } class WeekDay : public day{ private: virtual void doSomeThing(); void doFuckingWork(); }; void WeekDay::doSomeThing(){ doFuckingWork(); } class HoliDay : public day{ private: virtual void doSomeTning(); void playWithSon(); }; void HoliDay::doSomeThing(){ playWithSon(); }
こんな感じにしておけば、Dayを継承したWeekDayやHoliDayのdoTasks()を呼び出せば、平日はくそったれな仕事をするし、休日は息子と遊ぶことができます。
これはこれで目標であった"ある関数の一部だけを変えたい"を達成できた気もします。
実際これでいいときも多々ありますが、もう少し考えて見ます。
たとえば、会社がくそったれ過ぎて休日なのに出勤しなきゃならなかったりとか、平日にずる休みして息子と遊んでたりとか、っていうときにどうするかって問題があります。
Day *Jun_09 = new HoliDay(); // 土曜日 Day *Jun_10 = new WeekDay(); // 日曜だけど休日出勤 Day *Jun_11 = new HoliDay(); // 月曜だけどずる休み
って書いちゃうてもありますが、たとえば、このDayクラスが給料計算とかにも使われたりすると、休日出勤の手当てを計算するために日曜日はHoliDayで表現しておかなきゃならないし、休んだ分は給料からひかなきゃならないから月曜日はWeekDayで表現しておかなきゃなりません。
そんなときは、HoliDayクラスを継承したWorkingOnHoliDayとかを新たに定義するという方法もありますが、その方法だと、平日に海に行ったとか、休日に寝てたとか、そういうのも表そうとするとクラスの数が多くなりすぎちゃいます。
こんなときはstrategy patternで"何かをする"ってのを表すクラスを作ってそれをDayクラスのメンバにしちゃえばいいですね。
ここで、仮想関数の継承ではなく、切り替える関数を渡す的な方法を考えてみます。
class Day{ public: Day( void(*pf)()) {doSomething = pf;} void doTasks(){ preFunc(); doSomething(); postFunc(); } private: void preFunc(){ std::cout << "PRE" << std::endl; } void postFunc(){ std::cout << "POST" << std::endl; } void (*doSomething)(); }; void func(){ std::cout << "FUNC" << std::endl; } int main(){ Day* d = new Day(func); d->doTasks(); return 0; }
この例ではコンストラクタで渡すようにしてますがsetterを使っても大丈夫。
問題なのは、関数ポインタに渡す関数がDayクラスのプライベートメンバにアクセスできないことです。
”プライベートメンバにアクセスできない? じゃあfriendだ!”って一瞬思いましたが、
friend void (*doSomething)();
って書いたら
‘doSomething’ is neither function nor member function; cannot be declared friend
って怒られちゃったので、関数ポインタをfriendにすることは出来なそうです(もし出来る方法があれば教えていただきたいです)。
次の方法は、メンバ関数ポインタを渡す方法です
#include <iostream> class Day{ public: Day(){} void setFunc(void (Day::*pf)()){doSomething = pf;} void setX(int i){x = i;} void doTasks(){ preFunc(); (this->*doSomething)(); postFunc(); } void func1(){ std::cout << "func1, " << x << std::endl;} void func2(){ std::cout << "func2, " << x << std::endl;} private: int x; void preFunc(){ std::cout << "PRE" << std::endl; } void postFunc(){ std::cout << "POST" << std::endl; } void (Day::*doSomething)(); }; int main(){ Day* d = new Day(); void (Day::*func)() = &Day::func1; d->setX(10); d->setFunc(func); d->doTasks(); func = &Day::func2; d->setX(20); d->setFunc(func); d->doTasks(); return 0; }
http://www.geocities.jp/ky_webid/cpp/language/034.html
ここのサイトを参考させてもらいました。
これで、やりたいことは大体達成できたんですが、(this->*doSomething)();とかvoid (Day::*func)() = &Day::func1;とか若干読みにくいですね。
std::functionを使うと幾分かはましになります。
次に、関数オブジェクトを使う方法です。
#include <iostream> class C{ public: virtual void operator()(){ std::cout << "C" << std::endl; } }; class D : public C{ public: virtual void operator()(){ std::cout << "D" << std::endl; } }; class A{ public: A(C *c){this->m = c;} void func(){ (*m)(); } private: C *m; }; int main(){ C c; A a1(&c); a1.func(); D d; A a2(&d); a2.func(); return 0; }
一応こういう方法があるみたいですが、寡聞にして関数オブジェクトってstlのアルゴリズムに渡すっていうイメージしかなかったので、こう使えるんだーって思いました。
思いましたが、(*m)()ってやるくらいならoperator()でなはなくて普通のメンバ関数をオーバーライドしてやるほうがスマートに思います。
ながくなっちゃったので今回はここで終わります。
酔っ払った状態で書き始めて、一晩寝てから続きを書いたので前半と後半でテンションの差がひどいですね。