の前に
グローバル変数を
disる。
グローバル変数が
なぜダメか。
どこで誰が書き換えたか
よく分からないから。
class Person
{
private:
//Nameの書き換えができるのは
//このクラスメソッドだけ
std::string Name;
};
情報を制限する。
逆に言えば、
Name を適切に管理することは
Person クラスの責任である。
Name が変になったときは、
Personクラスが真っ先に疑われる。
そして、disられる。
Person テラカワイソス
いいクラス設計を
考える
の前に
ダメなクラスを
考える。
だめなクラスって
どんなクラス?
.cppが1万行ある
クラスを考える。
でかい
でかすぎる
どこで誰が何を
書き換えているのか
さっぱり
わかりません。
これでは、
メンバ変数が
グローバル変数と化す。
これの逆に
短いクラスを
考える。
ぢゃあ
どうするればいいの?
あれ?
これって関数と似てない?
反論:
これではクラス数が膨大になって、クラス数の爆発が起きませんか?
すっきり。
小さいクラスと
巨大なクラスの
見分け方は?
行数より
メンバ変数の数で
見てます。
もちろん、
例外もあります。
だけど、
人間は多くのものを
把握できないものです。
メンバ変数の爆発がダメなら、
メソッドの爆発はいいの?
ただし、
オブジェクトの状態を
変異させるメソッドには
注意が必要
setterを量産しては、
せっかく情報を隠した
意味がないです。
class Person
{
public:
Person(const std::string & inName)
: Name(inName)
{
}
std::string getName() const
{
return this->Name;
}
void setName
(const std::string & inName)
{
this->Name = inName;
}
private:
std::string Name;
};
情報を書き換えられる
メソッドは
できるだけ少なくします。
const!!
constメソッドは
自分はメンバ変数を
書き換えないと
宣言しているメソッドです。
class Person
{
public:
Person(const std::string & inName)
: Name(inName)
{
}
std::string getName() const
{
return this->Name;
}
private:
std::string Name;
};
状態が変異しないということは、
1000億回呼び出しても副作用がない。
それぞれを1000回呼び出すと?
なぜ
変更されるか、
変更されないかが
大事なの?
そもそもの議論を思い出す。
なぜグローバル変数が悪いのか。
どこで変更されるかわからない。
で、話を戻して、、
(*゚ ∀゚)=3ムッハー!
C++使っているなら
できるだけ
constメソッドを
量産しよう!!
継承について
継承は2つの意味があります。
C++だと
interface継承は純粋仮想クラス「だけ」で
構成したクラスという感じでしょうか。
class IPlayer
{
public:
virtual void Play() = 0;
};
interface継承は大歓迎
動画の再生ボタンと
CDプレイヤの再生ボタンは、
同じ再生だけどやることが違う
ボタンが押されたとき
class IPlayer
{
public:
virtual void Play() = 0;
};
class NetPlayer :
public IPlayer
{
virtual void Play()
{
ネットからダウンロードする
画像と音に変換する
画像を画面に出して
音を鳴らす。
}
};
class CD :
public IPlayer
{
virtual void Play()
{
CDを回転して読み込む
データから音に変換する
音を鳴らす
}
};
interface継承は
なので
どんどんやりましょう。
実装の追記である
abstract継承は
注意が必要
悪いとはいいませんが、
やりすぎに注意して。
なぜか?
継承はクラスの
足し算だからです。
クラス同士を結合し、
より大きなクラスを生成します。
class Base
{
protected:
int Count;
public:
virtual int countUp()
{
this->Count += 1;
}
int getCount const()
{
return this->Count;
}
}:
継承
class SuperCount :
public Base
{
private:
int UpCount;
public:
virtual int countUp()
{
this->Count += this->UpCount;
}
int setUpCount(int inUpCount)
{
this->UpCount = inUpCount;
}
};
継承せずに
書くとこうなる。
class SuperCount{
private:
int Count;
int UpCount;
public:
virtual int countUp()
{
this->Count += this->UpCount;
}
int getCount const()
{
return this->Count;
}
int setUpCount(int inUpCount)
{
this->UpCount = inUpCount;
}
}:
巨大なクラスに
近づいてしまう。
よく考えて
継承するようにします
委譲
is-a has-a の関係と
実装の継承
基本的に継承が
許されるのは、
is-aの関係のときです
has-a の時に、
継承すると
不思議なことになります
has-a の時には、
委譲を行います。
委譲のメリット
小技
クラス内で確保したリソースは、
特別な理由がない限り、
デストラクタで解放する
メモリが必要な場合は
std::vector で確保
char * p = new char[100];
strcpy(p , "11111111");
delete [] p:
class Buffer
{
public:
Buffer()
{
this->Buffer = NULL;
}
virtual ~Buffer()
{
delete [] this->Buffer;
}
void Alloc(uint size)
{
this->Buffer = new char[size];
}
};
ちなみに先ほどのようなクラスは不要ですなw
雑用クラスの
Utilは何かと便利。。。
雑多なモジュールを入れるクラスを
一つ作っておくと便利です。
今までの内容と矛盾する?
クラスに
切り出すほどでもない、
同じアルゴリズムを
開発者ごと、
クラスごとに作るのは
効率が悪い。
1プロジェクトに
1つはUtilクラス欲しいところ。
最初に作っとかないと
それぞれの開発者が
独自に作るので効率が悪い。
非スレッドセーフ上等
特別な理由がない限り、
スレッドセーフを
個々のクラスが
気にすることではありません。
逆に、
個々のクラスが
スレッドセーフ性を
考えなければいけないような
状態は設計がおかしい
組織で
別部門の人に仕事を
依頼するときに
マネージャを通すのと同じ
ロックの粒度を考えると、
直接やり取りしたほうがいい。
別部門への依頼も直接やり取りの方が早い。
だけど、ハンドルしきれなくなるのも不幸の始まり。
結局は、どっちを取るかは、
バランスになるのかなと。
トレードオフですね。
書き込み可能な
シングルトンは要注意
singleton を利用すれば
クラスを超えて
データを
持ち運ぶことができます
でかいことは
悪いことだ。
これだけわかれば、
あとは大丈夫!!
たぶん。
えんいー\e
誰が書き換えたのか
よく分からないから
追跡ができない。
そこでデータの
カプセル化/情報の隠匿。
クラス設計のお話
~反論大歓迎www~
(クラスの持つ性質の2つのうちの1つ)
by rti
誰が管理しているのか、
誰が悪いのか、
責任の所在が
はっきりしない。
Name にアクセスし書き換えることが
できる機会を少なくすることで、
不用意なデータの書き換えで
プログラムがおかしくなることを防ぐ。
↑重要
Tシャツもあるよ
悲しいけど、でも、
これプログラムなんだよね。
長いクラス
巨大なクラス
↓ここから落としてみてね
http://rtilabs.net/files/2010_05_31/bigclass.cpp.txt
自由に使えるサンプルがなかったので、
自分で作ったプログラムを結合しまくって、
巨大なクラスを作りましたwww
クラス
やりたいこと
クラス
でかい
クラス
クラス
呼び出す
クラス
小さいクラスをたくさん作ります。
それを組み合わせて、
複雑なことを行います。
メンバ変数が
class
{
private:
int Age;
std::string Name;
std::string Tel;
std::string Address;
std::string Mail;
};
class Person
{
public:
Person(const std::string & inName)
: Name(inName)
{
}
std::string getName() const
{
return this->Name;
}
private:
//これを操作できるのは Personクラスだけ
std::string Name;
};
class
{
private:
int Age;
std::string Name;
std::string Tel;
std::string Address;
std::string Mail;
int Number;
int Salary;
std::string Section
std::string Title;
int Birthday;
};
つまり。
5個以上にぐらいで黄色信号
10個ぐらいになると赤信号
でかいクラスはダメなのです。
ダメな理由はグローバル変数が
ダメな理由と一緒です。
メンバー変数5個
rtiの独断と偏見にみちた基準
メンバー変数10個
どこでNameを書き換えているか
一発で分かります。
socket
db
クラス
namespace(C++) や
package(C#等) で
グルーピングします
クラス
html
クラス
私は手抜きなんで気に
しないんですけどw
クラス
小さいクラスがたくさん
グルーピング
邪悪
メソッドの爆発は
とりあえず大丈夫。
setter
これもrti基準
ネットからダウンロードする
画像と音に変換する
画像を画面に出して
音を鳴らす。
CDを回転させてデータを読み込む
データから音に変換する
音を鳴らす
ここまでのまとめ
カプセル化してデータを隠そう。
カプセル化はクラスの2つの特性のうち1つ。
やることが違っても
constメソッドは、
C++で一番キュートで
セクシーな機能
ユーザーは同じ
キリッ
でかいクラスはよくない。
大きいことは悪いことだ。
class Person
{
public:
Person(const std::string & inName)
: Name(inName)
{
}
std::string getName() const
{
this->Name = "書き換え";
}
private:
std::string Name;
};
constかわいいよconst
インターフェースで
操作ができる。
状態を変異させるのはよくない。
constはかわいい。
かわいいは正義
これも独断と偏見ですw
再生ボタン
コンパイルエラーになる
誰が責任を取るのかを明確に
実装の継承
クラスの差分を追記する。
変異しない
C#でいう abstract継承
Person person;
int i ;
for (i = 0 ;i < 1000; i++)
{
person.getName();
person.countUp();
}
class Person
{
public:
Person(const std::string & inName)
: Name(inName) , Count(0)
{
}
std::string getName() const
{
return this->Name;
}
int countUp()
{
return ++this->Count;
}
private:
std::string Name;
int Count;
};
インターフェース継承
インターフェースを定義する。
変更されないというのは
その真逆ともいえる。
C#でいう interface継承
Name は変更はないが、
Count は 1000になる。
共通のインターフェースで
操作することができる。
これは何?
変異する
Play
これをポリモーフィズムといって、
日本語では多様性とかいいます。
大歓迎
(クラスの持つ性質の2つのうちのもう1つ)
is-a
同じカテゴリに含まれるような関係
「車」は「乗り物」のカテゴリ
has-a
持っている、所有しているのよう関係
is-aの関係
is-aの関係??
「車」は「車輪」をもっています
「車」 は 「乗り物」である。
「車」 は 「車輪」である???
個人的に、
継承ではないので
クラスの足し算が行われない。
アンチ実装の継承
//タイヤ
class Tire
{
};
なので、委譲大好きです。
//車はタイヤを4つもってる
class car
{
private:
Tire tire[4];
};
has-aの関係
クラスは巨大にならない
継承は数えるほどしかしませんが、
委譲は山のようにします。
委譲
has-aの関係!?
「車」 は 「車輪」をもっている
「車」 は 「乗り物」を持っている??
ここまでのまとめ
継承にはinterfaceの継承と、
実装の継承がある。
実装の継承はクラスの足し算になる。
継承は is-a の時にやって、
has-a のときは委譲しよう。
inerfaceの継承はクラスの特性の一つ
ポリモーフィズムを実現する。
std::vector<char> p(100);
strcpy( &p[0] , "11111111");
deleteし忘れがなくなるよ!!
std::vectorのみ &p[0] したときの連続性は保証されているよ。
アルゴリズムの重複
状態を持ちません。
(メンバ変数を持たない)
class Util
{
public:
//単語の数を数える
static void WordCount
{
}
};
class MyClassA
{
private:
int MyWordCount()
{
}
};
汎用的な処理がクラスに
ばら撒かれるのを防げます。
class MyClassB
{
private:
int OreOreWordCount()
{
}
};
似たような処理がたくさんできたら、
別クラスとして分離し独立させる
こともできます。
public static なのでどこからでも
インスタンス作成なしで呼べます。
Util::WordCount("/a/a/a","/");
マネージャを通す。
非スレッドセーフ
直接やり取りは効率がいいが
直接やり取り
設計が
おかしい
A部門
B部門
A部門
B部門
クラス
スレッド
クラス
スレッド
クラス
マ
ネ
ー
ジ
ャ
クラス
マ
ネ
ー
ジ
ャ
?
クラス
Xさん
クラス
Yさん
クラス
仕事の依頼
クラス
仕事の依頼
B部門マネージャ
?
B部門マネージャ
コイツが
スレッドセーフにする
責任を追う
マネージャがハンドルしきれない
誰も責任を取らない。。。。
マネージャ仕事しろ!!
書き込みsingletonには注意
int memoryLimit =
MySingleton::
getInstace()->
ReadMemoryLimit();
読み込みは仕方ない。
MySingleton::
getInstace()->
SetMemoryLimit(1024);
だって便利だし。。。
便利なんですが、
これってある意味
グローバル変数ですよね。
グローバル変数の悪夢再び
カプセル化
全体の
誰がデータを
書き換える権利/責任が
あるかを明確にします。
まとめ
ポリモーフィズ
別々のものを
同一インターフェースで、
利用できるようにします。