beatle様
返事が遅くなってすみません。そして、何度もどうもありがとうございます。
継承ではなく外に出しておくのがいいというのは、とても同意します。
自分でも4つほど実装を書いてみたのですが、やはり継承というのはクラスの派生に使うものなので、
“関数を追加する”ように使うのは間違っていることがわかりました。
オブジェクト指向に慣れていると当然のことなのだと思われますが、
自分はどうもそのあたりが上手く理解できないようです。
様々な状況で、色々な組み合わせで必要になるメンバ変数や構造体について、
クラスにたくさんメンバ変数や構造体を持たせておいて、そのクラスにメソッドを次々追加し、
メンバ変数や構造体の操作を実装するというのは、ただのC言語+クラス構造ですね。
自分が調べた限りでは、c++でのオブジェクト指向の本当に良い方法というのは、
たくさんあるメンバ及びそれを操作するメソッドを、上手く切り分けて小さなクラスに分割し、
必要な時に Setter や Getter でメンバを設定・取得することであるというような印象を受けました。
やはりまだ圧倒的に経験が足りな過ぎたようなので、これからも試行錯誤して考えてみます。
例えに出したものとだいぶ違うのですが、色々試した結果のコードを投稿させていただきます
この掲示板の連投規制や文字数制限がよくわからなかったので、時間を開けて投稿します。
ご迷惑をおかけしたらすみません。
動体クラス MovingBody には、安定状態チェックの CheckStability() 動作の Move() 現在地取得の GetPos()
の関数が実装されています。それらの関数は MovingBody の様々な場所で参照される構造体を操作します(という設定です)。
以下が MovingBody を使用するソースコードです。
4つの MovingBody のインスタンスを作り、時間が経過する毎に安定状態チェックを行い、移動させ、座標を表示します。
コード:
// main.cpp
#include "implement_1.h"
#include <vector>
static const int MAX_TIME = 20;
int main()
{
std::vector<MovingBody*> Movings;
Movings.push_back(new Car);
Movings.push_back(new Motorcycle);
Movings.push_back(new Skateboard);
Movings.push_back(new Bike);
std::vector<MovingBody*>::iterator it;
for(int time=0; time<MAX_TIME; time++)
{
for (it = Movings.begin(); it != Movings.end(); it++)
{
(*it)->CheckStability();
(*it)->Move();
(*it)->GetPos();
}
std::cout << std::endl;
}
char c;
std::cin >> c;
return 0;
}
以下が MovingBody が持つ構造体です
コード:
// params.h
// MovingObjectの持つ情報
struct KineticParameters
{
int x, vel;
KineticParameters() : x(0), vel(0) {};
};
struct GeneralStatus
{
bool isStable, isControllable;
GeneralStatus() : isStable(false), isControllable(false) {};
};
以下が多重継承による MovingBody の実装です。
コード:
// implement_1.h
// 多重継承による実装
// 失敗:ダイアモンド継承で個別に純粋仮想関数を実装できない
// 動作はするが、2つ以上の同じ名前を持つ関数があると警告が出る
#include <iostream>
#include <string>
#include "params.h"
// 動体基底クラス
class MovingBody
{
protected:
KineticParameters KinPrms;
GeneralStatus Status;
public:
void GetPos(){ std::cout << KinPrms.x << '\t'; };
virtual void Move() = 0;
virtual void CheckStability() = 0;
};
// Move() 関数追加クラス 1
class EngineDriveBody : virtual public MovingBody
{
public:
// 共通の移動関数の実装 1
// ――――――――――――――――――――――――――――――――
void Move()
{
KinPrms.x += ++KinPrms.vel;
}
// ――――――――――――――――――――――――――――――――
};
// Move() 関数追加クラス 2
class LegMuscleDriveBody : virtual public MovingBody
{
public:
// 共通の移動関数の実装 2
// ――――――――――――――――――――――――――――――――
void Move()
{
if(KinPrms.vel <= 0) KinPrms.vel = 20;
KinPrms.x += --KinPrms.vel;
}
// ――――――――――――――――――――――――――――――――
};
// CheckStability() 関数追加クラス 1
class TwoWheeledBody : virtual public MovingBody
{
public:
// 共通の安定チェック関数の実装 1
// ――――――――――――――――――――――――――――――――
void CheckStability()
{
Status.isStable = KinPrms.vel > 5;
Status.isControllable = KinPrms.vel < 15;
std::string msg = (Status.isStable) ? "s___" : "us__" ;
msg += (Status.isControllable) ? "c___" : "uc__" ;
std::cout << msg ;
}
// ――――――――――――――――――――――――――――――――
};
// CheckStability() 関数追加クラス 2
class FourWheeledBody : virtual public MovingBody
{
public:
// 共通の安定チェック関数の実装 2
// ――――――――――――――――――――――――――――――――
void CheckStability()
{
Status.isStable = KinPrms.vel < 1000;
Status.isControllable = KinPrms.vel < 1500;
std::string msg = (Status.isStable) ? "s___" : "us__" ;
msg += (Status.isControllable) ? "c___" : "uc__" ;
std::cout << msg ;
}
// ――――――――――――――――――――――――――――――――
};
// 共通の関数を持つクラス 1-1
class Car : public EngineDriveBody, public FourWheeledBody
{
};
// 共通の関数を持つクラス 1-2
class Motorcycle : public EngineDriveBody, public TwoWheeledBody
{
};
// 共通の関数を持つクラス 2-1
class Skateboard : public LegMuscleDriveBody, public FourWheeledBody
{
};
// 共通の関数を持つクラス 2-2
class Bike : public LegMuscleDriveBody, public TwoWheeledBody
{
};
失敗です。基底クラスで純粋仮想関数 Move() を宣言し、片方の継承 (Engine または LegMuscle) で実装しても、
もう片方の (TwoWheeled, FourWheeled)にもMove() が継承されているので、
warning C4250 “2 つ以上のメンバーが同じ名前を持っています。”の警告が出ます。
かといって MovingBody に Move() などの関数を定義していないと、
MovingBody* obj = new Car;
obj->Move()
のように関数を使うことができません。ほぼ多重継承を使ったことがなかったので誤解していました。
以下は Strategy パターンによる実装です。
コード:
// implement_2.h
// Strategy パターンによる実装
#include<iostream>
#include<string>
#include "params.h"
// 移動方策
class MoveStrategy
{
public:
virtual void Move(KineticParameters* KinPrms) = 0;
};
// 移動方策クラス 1
class ByEngine : public MoveStrategy
{
public:
// 共通の移動関数の実装 1
// ――――――――――――――――――――――――――――――――
void Move(KineticParameters* KinPrms)
{
KinPrms->x += ++KinPrms->vel;
}
// ――――――――――――――――――――――――――――――――
};
// 移動方策クラス 2
class ByLegMuscle : public MoveStrategy
{
public:
// 共通の移動関数の実装 2
// ――――――――――――――――――――――――――――――――
void Move(KineticParameters* KinPrms)
{
if(KinPrms->vel <= 0) KinPrms->vel = 20;
KinPrms->x += --KinPrms->vel;
}
// ――――――――――――――――――――――――――――――――
};
// 安定化方策
class StabilizeStrategy
{
public:
virtual void CheckStability(GeneralStatus* Status, int vel) = 0;
};
// 安定化方策クラス 1
class EquipTwoWheel : virtual public StabilizeStrategy
{
public:
// 共通の安定チェック関数の実装 1
// ――――――――――――――――――――――――――――――――
void CheckStability(GeneralStatus* Status, int vel)
{
Status->isStable = vel > 5;
Status->isControllable = vel < 15;
std::string msg = (Status->isStable) ? "s___" : "us__" ;
msg += (Status->isControllable) ? "c___" : "uc__" ;
std::cout << msg ;
}
// ――――――――――――――――――――――――――――――――
};
// 安定化方策クラス 2
class EquipFourWheel : virtual public StabilizeStrategy
{
public:
// 共通の安定チェック関数の実装 2
// ――――――――――――――――――――――――――――――――
void CheckStability(GeneralStatus* Status, int vel)
{
Status->isStable = vel < 1000;
Status->isControllable = vel < 1500;
std::string msg = (Status->isStable) ? "s___" : "us__" ;
msg += (Status->isControllable) ? "c___" : "uc__" ;
std::cout << msg ;
}
// ――――――――――――――――――――――――――――――――
};
// 動体基底クラス
class MovingBody
{
protected:
KineticParameters KinPrms;
GeneralStatus Status;
MoveStrategy* MyMoveStrategy;
StabilizeStrategy* MyStabilizeStrategy;
MovingBody( MoveStrategy* MovSt, StabilizeStrategy* StablSt ) :
MyMoveStrategy(MovSt), MyStabilizeStrategy(StablSt) {};
public:
void Move(){ MyMoveStrategy->Move(&KinPrms); };
void CheckStability(){ MyStabilizeStrategy->CheckStability(&Status, KinPrms.vel); };
void GetPos(){ std::cout << KinPrms.x << '\t'; };
};
// 共通の関数を持つクラス 1-1
class Car : public MovingBody
{
public:
Car() : MovingBody( new ByEngine(), new EquipFourWheel() ) {}
};
// 共通の関数を持つクラス 1-2
class Motorcycle : public MovingBody
{
public:
Motorcycle() : MovingBody( new ByEngine(), new EquipTwoWheel() ) {}
};
// 共通の関数を持つクラス 2-1
class Skateboard : public MovingBody
{
public:
Skateboard() : MovingBody( new ByLegMuscle(), new EquipFourWheel() ) {}
};
// 共通の関数を持つクラス 2-2
class Bike : public MovingBody
{
public:
Bike() : MovingBody( new ByLegMuscle(), new EquipTwoWheel() ) {}
};
基底クラスが非常に重たい見た目です。関数は処理に必要な変数や構造体を引数にしています。
この後作った包含の実装でも関数の実装の部分は大体同じです。
続きます。