Stateパターン
状態の変化に応じて振る舞いを変える
State とは、英語で「状態」を意味する単語です。オブジェクト指向設計では、モノをクラスとして表現することが多くあります。しかし、State パターンとは、 モノではなく、「状態」をクラスとして表現するパターンです。
状態によって、動作のパターンが変わることがよくあります。
例えば、「機嫌のいい状態」「機嫌が悪い状態」の2つの状態があるお母さんにいくつか頼みごとをすることを考えます。機嫌のいい状態のお母さんに「お小遣い頂戴」「ケーキ買って」などのお願いをした場合、「はいはい」といってお小遣いをくれたり、ケーキを買ってくれたりするでしょう。
しかし、機嫌の悪い状態のお母さんにこれらのお願いをしても聞き入れてくれないかもしれません。 お母さんは状態によって、振る舞いが変わるわけです。
State パターンとは、このような、状態の変化に応じて振る舞いが変わるような場合に威力を発揮するパターンです。
Stateパターンを適用する目的は、「オブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。クラス内では、振る舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する」ことです。「クラス内で振る舞いの変化を記述する」ことは問題だと考えられているわけです。パターンを適用するには、このようなことが問題であることに気づく技術が、開発者には必要です。問題であると気づかなければ、パターンを適用することを思いつくこともないでしょう。パターンを適用しなくてもプログラムは動作するからです。したがって、プログラムを変更するという作業を始めるには、「状態の数が増えたとき、これ以上プログラムの複雑度を上げてはいけない」という意識が必要です。
例題
時刻によって異なる挨拶を表示するクラスを作りなさい。
4時~10時(朝) おはよう、さようなら
10時~19時(昼) こんにちは、さようなら
19時~ 4時(夜) こんばんは、おやすみ
おはよう
さようなら
14時
こんにちは
さようなら
21時
こんばんは
おやすみ
|
Stateパターンを使用しない例 Greeting.java public class Greeting {
private int state;
public void setClock(int hour) {
System.out.println(hour + "時");
if (hour < 4 || hour >= 19) {
state = 3;
}
else if (hour < 10) {
state = 1;
}
else {
state = 2;
}
System.out.println(encounter());
System.out.println(parting());
}
public String encounter() {
if (state == 1) {
return "おはよう";
}
else if (state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
public String parting() {
if (state == 1) {
return "さようなら";
}
else if (state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
}
Main.java public class Main {
public static void main(String[] args) {
Greeting g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
}
}
|
Stateパターンを使用した例 State.java public abstract class State {
public void clock(IContext context, int hour) {
if (hour >= 4 && hour < 10)
context.changeState(MorningState.getInstance());
else if (hour >= 10 && hour < 19)
context.changeState(DayState.getInstance());
else if (hour >= 20 || hour < 4)
context.changeState(NightState.getInstance());
}
public abstract void encounter(IContext context);
public abstract void parting(IContext context);
}
MorningState.java public class MorningState extends State {
private static MorningState singleton = new MorningState();
public static State getInstance() {
return singleton;
}
public void encounter(IContext context) {
context.encounter("おはよう");
}
public void parting(IContext context) {
context.parting("さようなら");
}
}
DayState.java public class DayState extends State {
private static DayState singleton = new DayState();
public static State getInstance() {
return singleton;
}
public void encounter(IContext context) {
context.encounter("こんにちは");
}
public void parting(IContext context) {
context.parting("さようなら");
}
}
NightState.java public class NightState extends State {
private static NightState singleton = new NightState();
public static State getInstance() {
return singleton;
}
public void encounter(IContext context) {
context.encounter("こんばんは");
}
public void parting(IContext context) {
context.parting("おやすみ");
}
}
IContext.java public interface IContext {
public void setClock(int hour);
public void changeState(State state);
public void encounter(String msg);
public void parting(String msg);
}
Greeting.java public class Greeting implements IContext {
private State state = NightState.getInstance();
public void setClock(int hour) {
System.out.println(hour + "時");
state.clock(this, hour);
state.encounter(this);
state.parting(this);
}
public void changeState(State state) {
this.state = state;
}
public void encounter(String msg) {
System.out.println(msg);
}
public void parting(String msg) {
System.out.println(msg);
}
}
Main.java public class Main {
public static void main(String[] args) {
Greeting g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
}
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.h #include <string>
class Greeting
{
private:
int state;
public:
Greeting(void);
virtual ~Greeting(void);
void setClock(int);
std::string encounter(void);
std::string parting(void);
}; greeting.cpp #include <iostream>
using namespace std;
#include "greeting.h"
Greeting::Greeting(void) {}
Greeting::~Greeting(void) {}
void Greeting::setClock(int hour) {
cout << hour << "時" << endl;
if (hour < 4 || hour >= 19) {
state = 3;
}
else if (hour < 10) {
state = 1;
}
else {
state = 2;
}
cout << encounter() << endl;
cout << parting() << endl;
}
string Greeting::encounter(void) {
if (state == 1) {
return "おはよう";
}
else if (state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
string Greeting::parting(void) {
if (state == 1) {
return "さようなら";
}
else if (state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
main.cpp #include "greeting.h"
int main()
{
Greeting g;
g.setClock(7);
g.setClock(14);
g.setClock(21);
return 0;
}
|
Stateパターンを使用した例 state.h #include "greeting.h"
class State
{
public:
state(void);
virtual ~State(void);
void clock(Greeting*, int);
virtual void encounter(Greeting*) = 0;
virtual void parting(Greeting*) = 0;
}; state.cpp #include "state.h"
#include "Morningstate.h"
#include "Daystate.h"
#include "Nightstate.h"
State::state(void) {}
State::~State(void) {}
void State::clock(Greeting* g, int hour) {
if (hour >= 4 && hour < 10)
g->changeState(MorningState::getInstance());
else if (hour >= 10 && hour < 19)
g->changeState(DayState::getInstance());
else if (hour >= 20 || hour < 4)
g->changeState(NightState::getInstance());
} morningState.h #include "state.h"
class MorningState : public State
{
private:
static MorningState* singleton;
public:
MorningState(void);
virtual ~MorningState(void);
static State* getInstance(void);
void encounter(Greeting*);
void parting(Greeting*);
}; morningState.cpp #include "morningState.h"
MorningState* MorningState::singleton = new MorningState();
MorningState::MorningState(void) {}
MorningState::~MorningState(void) {}
State* MorningState::getInstance(void) { return singleton; }
void MorningState::encounter(Greeting* g) { g->encounter("おはよう"); }
void MorningState::parting(Greeting* g) { g->parting("さようなら"); } dayState.h #include "state.h"
class DayState : public State
{
private:
static DayState* singleton;
public:
DayState(void);
virtual ~DayState(void);
static State* getInstance(void);
void encounter(Greeting*);
void parting(Greeting*);
}; dayState.cpp #include "dayState.h"
DayState* DayState::singleton = new DayState();
DayState::DayState(void) {}
DayState::~DayState(void) {}
State* DayState::getInstance(void) { return singleton; }
void DayState::encounter(Greeting* g) { g->encounter("こんにちは"); }
void DayState::parting(Greeting* g) { g->parting("さようなら"); } nightState.h #include "state.h"
class NightState : public State
{
private:
static NightState* singleton;
public:
NightState(void);
virtual ~NightState(void);
static State* getInstance(void);
void encounter(Greeting*);
void parting(Greeting*);
}; nightState.cpp #include "nightState.h"
NightState* NightState::singleton = new NightState();
NightState::NightState(void) {}
NightState::~NightState(void) {}
State* NightState::getInstance(void) { return singleton; }
void NightState::encounter(Greeting* g) { g->encounter("こんばんは"); }
void NightState::parting(Greeting* g) { g->parting("おやすみ"); } Greeting.h #include <string>
class State;
class Greeting
{
private:
State* st;
public:
Greeting(void);
virtual ~Greeting(void);
void setClock(int);
void changeState(State*);
void encounter(std::string);
void parting(std::string);
}; Greeting.cpp #include <iostream>
using namespace std;
#include "Greeting.h"
#include "nightState.h"
Greeting::Greeting(void) {
st = NightState::getInstance();
}
Greeting::~Greeting(void) {}
void Greeting::setClock(int hour) {
cout << hour << "時" << endl;
st->clock(this, hour);
st->encounter(this);
st->parting(this);
}
void Greeting::changeState(State* st) {
this->st = st;
}
void Greeting::encounter(string msg) {
cout << msg << endl;
}
void Greeting::parting(string msg) {
cout << msg << endl;
} main.cpp #include "greeting.h"
int main()
{
Greeting g;
g.setClock(7);
g.setClock(14);
g.setClock(21);
return 0;
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter )にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。 if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです 。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。また、この部分をChain of Responsibilityパターンで記述すれば、さらに変更する部分は減るでしょう(自分の時間帯でなければ次の時間帯に処理を委譲する)。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 st->encounter(this); st->parting(this); というように、条件にあったインスタンスのメソッドが呼ばれることになります(state に MorningState の インスタンスがセットされている)。 この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移を Greeting クラスに任せてしまうこともできます。そうすれば、個々の State のサブクラスの独立性が高まります。しかし、それでは Greeting クラスがすべての State のサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.cs class Greeting
{
private int state;
public void SetClock(int hour)
{
Console.WriteLine(hour + "時");
if (hour < 4 || hour >= 19)
{
state = 3;
}
else if (hour < 10)
{
state = 1;
}
else
{
state = 2;
}
Console.WriteLine(Encounter());
Console.WriteLine(Parting());
}
private string Encounter()
{
switch (state)
{
case 1:
return "おはよう";
case 2:
return "こんにちは";
default:
return "こんばんは";
}
}
private string Parting()
{
switch (state)
{
case 1:
return "さようなら";
case 2:
return "さようなら";
default:
return "おやすみ";
}
}
} Program.cs class Program
{
static void Main(string[] args)
{
Greeting g = new Greeting();
g.SetClock(7);
g.SetClock(14);
g.SetClock(21);
}
}
|
Stateパターンを使用した例 State.cs abstract class State
{
public void Clock(Greeting g, int hour)
{
if (hour >= 4 && hour < 10)
{
g.ChangeState(MorningState.GetInstance());
}
else if (hour >= 10 && hour < 19)
{
g.ChangeState(DayState.GetInstance());
}
else
{
g.ChangeState(NightState.GetInstance());
}
}
public abstract void Encounter(Greeting g);
public abstract void Parting(Greeting g);
} MorningState.cs class MorningState : State
{
private static MorningState instance = new MorningState();
private MorningState() { }
public static State GetInstance()
{
return instance;
}
public override void Encounter(Greeting g)
{
g.Encounter("おはよう");
}
public override void Parting(GGreeting g)
{
g.Parting("さようなら");
}
} DayState.cs class DayState : State
{
private static DayState instance = new DayState();
private DayState() { }
public static State GetInstance()
{
return instance;
}
public override void Encounter(Greeting g)
{
g.Encounter("こんにちは");
}
public override void Parting(Greeting g)
{
g.Parting("さようなら");
}
} NightState.cs class NightState : State
{
private static NightState instance = new NightState();
private NightState() { }
public static State GetInstance()
{
return instance;
}
public override void Encounter(Greeting g)
{
g.Encounter("こんばんは");
}
public override void Parting(Greeting g)
{
g.Parting("おやすみ");
}
} Greeting.cs class Greeting
{
private State state = NightState.GetInstance();
public void SetClock(int hour)
{
Console.WriteLine(hour + "時");
state.Clock(this, hour);
state.Encounter(this);
state.Parting(this);
}
public void ChangeState(State state)
{
this.state = state;
}
public void Encounter(string msg)
{
Console.WriteLine(msg);
}
public void Parting(string msg)
{
Console.WriteLine(msg);
}
} Program.cs class Program
{
static void Main(string[] args)
{
Greeting g = new Greeting();
g.SetClock(7);
g.SetClock(14);
g.SetClock(21);
}
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(Encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(Parting)にも if 文で分岐させて、というように作ってあります。 if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです 。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。また、この部分をChain of Responsibilityパターンで記述すれば、さらに変更する部分は減るでしょう(自分の時間帯でなければ次の時間帯に処理を委譲する)。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、ChangeState というメソッドに引き渡され、そこで、 state.Encounter(this); state.Parting(this); というように、条件にあったインスタンスのメソッドが呼ばれることになります(state に MorningState の インスタンスがセットされている)。 この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移を Greeting クラスに任せてしまうこともできます。そうすれば、個々の State のサブクラスの独立性が高まります。しかし、それでは Greeting クラスがすべての State のサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.vb Public Class Greeting
Private state As Integer
Public Sub SetClock(ByVal hour As Integer)
Console.WriteLine(hour & "時")
If hour < 4 OrElse hour >= 19 Then
state = 3
ElseIf hour < 10 Then
state = 1
Else : state = 2
End If
Console.WriteLine(Encounter())
Console.WriteLine(Parting())
End Sub
Private Function Encounter() As String
If state = 1 Then
Return "おはよう"
ElseIf state = 2 Then
Return "こんにちは"
Else
Return "こんばんは"
End If
End Function
Private Function Parting() As String
If state = 1 Then
Return "さようなら"
ElseIf state = 2 Then
Return "さようなら"
Else
Return "おやすみ"
End If
End Function
End Class
Program.vb Module Main
Sub Main()
Dim g As Greeting = New Greeting()
g.SetClock(7)
g.SetClock(14)
g.SetClock(21)
End Sub
End Module
|
Stateパターンを使用した例 State.vb Public MustInherit Class State
Public Sub Clock(ByVal g As Greeting, hour As Integer)
If hour >= 4 AndAlso hour < 10 Then
g.ChangeState(MorningState.GetInstance())
ElseIf hour >= 10 AndAlso hour < 19 Then
g.ChangeState(DayState.GetInstance())
Else
g.ChangeState(NightState.GetInstance())
End If
End Sub
Public MustOverride Sub Encounter(ByVal g As Greeting)
Public MustOverride Sub Parting(ByVal g As Greeting)
End Class
MorningState.vb Public Class MorningState
Inherits State
Private Shared instance As MorningState = New MorningState()
Private Sub New()
End Sub
Public Shared Function GetInstance() As State
Return instance
End Function
Public Overrides Sub Encounter(ByVal g As Greeting)
g.Encounter("おはよう")
End Sub
Public Overrides Sub Parting(ByVal g As Greeting)
g.Parting("さようなら")
End Sub
End Class
DayState.vb Public Class DayState
Inherits State
Private Shared instance As DayState = New DayState()
Private Sub New()
End Sub
Public Shared Function GetInstance() As State
Return instance
End Function
Public Overrides Sub Encounter(ByVal g As Greeting)
g.Encounter("こんにちは")
End Sub
Public Overrides Sub Parting(ByVal g As Greeting)
g.Parting("さようなら")
End Sub
End Class
NightState.vb Public Class NightState
Inherits State
Private Shared instance As NightState = New NightState()
Private Sub New()
End Sub
Public Shared Function GetInstance() As State
Return instance
End Function
Public Overrides Sub Encounter(ByVal g As Greeting)
g.Encounter("こんばんは")
End Sub
Public Overrides Sub Parting(ByVal g As Greeting)
g.Parting("おやすみ")
End Sub
End Class
Greeting.vb Public Class Greeting
Private state As State = NightState.GetInstance()
Public Sub SetClock(ByVal hour As Integer)
Console.WriteLine(hour & "時")
state.Clock(Me, hour)
state.Encounter(Me)
state.Parting(Me)
End Sub
Public Sub ChangeState(ByVal state As State)
Me.state = state
End Sub
Public Sub Encounter(ByVal msg As String)
Console.WriteLine(msg)
End Sub
Public Sub Parting(ByVal msg As String)
Console.WriteLine(msg)
End Sub
End Class
Program.vb Module Main
Sub Main()
Dim g As Greeting = New Greeting()
g.SetClock(7)
g.SetClock(14)
g.SetClock(21)
End Sub
End Module
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(Encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(Parting)にも if 文で分岐させて、というように作ってあります。 if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです 。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。また、この部分をChain of Responsibilityパターンで記述すれば、さらに変更する部分は減るでしょう(自分の時間帯でなければ次の時間帯に処理を委譲する)。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、ChangeState というメソッドに引き渡され、そこで、 state.Encounter(Me) state.Parting(Me) というように、条件にあったインスタンスのメソッドが呼ばれることになります(state に MorningState の インスタンスがセットされている)。 この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移を Greeting クラスに任せてしまうこともできます。そうすれば、個々の State のサブクラスの独立性が高まります。しかし、それでは Greeting クラスがすべての State のサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.js module.exports = class Greeting {
setClock(hour) {
process.stdout.write(hour + "時\n");
if (hour < 4 || hour >= 19) {
this.state = 3;
}
else if (hour < 10) {
this.state = 1;
}
else {
this.state = 2;
}
process.stdout.write(this.encounter() + "\n");
process.stdout.write(this.parting() + "\n");
}
encounter() {
if (this.state == 1) {
return "おはよう";
}
else if (this.state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
parting() {
if (this.state == 1) {
return "さようなら";
}
else if (this.state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
}
Main.js const Greeting = require("./Greeting.js");
let g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
|
Stateパターンを使用した例 State.js module.exports = class State {
clock(context, hour) {}
encounter(context) {}
parting(context) {}
}
MorningState.js const State = require("./State.js");
const DayState = require("./DayState.js");
module.exports = class MorningState extends State {
static singleton = new MorningState();
static getInstance() {
return MorningState.singleton;
}
clock(context, hour) {
if (hour >= 4 && hour < 10)
context.changeState(MorningState.getInstance());
else
DayState.getInstance().clock(context, hour);
}
encounter(context) {
context.encounter("おはよう");
}
parting(context) {
context.parting("さようなら");
}
}
DayState.js const State = require("./State.js");
const NightState = require("./NightState.js");
module.exports = class DayState extends State {
static singleton = new DayState();
static getInstance() {
return DayState.singleton;
}
clock(context, hour) {
if (hour >= 10 && hour < 19)
context.changeState(DayState.getInstance());
else
NightState.getInstance().clock(context, hour);
}
encounter(context) {
context.encounter("こんにちは");
}
parting(context) {
context.parting("さようなら");
}
}
NightState.js const State = require("./State.js");
module.exports = class NightState extends State {
static singleton = new NightState();
static getInstance() {
return NightState.singleton;
}
clock(context, hour) {
context.changeState(NightState.getInstance());
}
encounter(context) {
context.encounter("こんばんは");
}
parting(context) {
context.parting("おやすみ");
}
}
IContext.js module.exports = class IContext {
setClock(hour) {}
changeState(state) {}
encounter(msg) {}
parting(msg) {}
}
Greeting.js const IContext = require("./IContext.js");
const MorningState = require("./MorningState.js");
module.exports = class Greeting extends IContext {
setClock(hour) {
this.state = MorningState.getInstance();
process.stdout.write(hour + "時\n");
this.state.clock(this, hour);
this.state.encounter(this);
this.state.parting(this);
}
changeState(state) {
this.state = state;
}
encounter(msg) {
process.stdout.write(msg + "\n");
}
parting(msg) {
process.stdout.write(msg + "\n");
}
}
Main.js const Greeting = require("./Greeting.js");
let g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State を継承したクラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (this.state に MorningState の インスタンスがセットされている) この例では、State クラスの各サブクラスに clock メソッドが存在していて、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲するというようにしています(Chain of Responsibilityパターン)。 ということは、サブクラスは、チェーンの次のサブクラスのことを知っている必要があるということです。 これは、将来 EveningState クラスを追加することになったら、DayState クラスも修正する必要があるということです。つまり、サブクラスに状態遷移をまかせてしまうと、サブクラス間の依存関係を深めてしまうことになります。 別の方法として、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 注意:State クラスでサブクラスを、サブクラスで State クラスをそれぞれ require すると、循環参照になり実行時にエラーになります。 |
|
Stateパターンを使用しない例 Greeting.pm package Greeting {
sub new {
my ($class) = @_;
return bless {}, $class;
}
sub setClock {
my ($this, $hour) = @_;
print "${hour}時\n";
if ($hour < 4 || $hour >= 19) {
$this->{state} = 3;
}
elsif ($hour < 10) {
$this->{state} = 1;
}
else {
$this->{state} = 2;
}
print $this->encounter() . "\n";
print $this->parting() . "\n";
}
sub encounter {
if ($this->{state} == 1) {
return "おはよう";
}
elsif ($this->{state} == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
sub parting {
if ($this->{state} == 1) {
return "さようなら";
}
elsif ($this->{state} == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
}
1;
Main.pl use lib qw(./);
use Greeting;
my $g = new Greeting();
$g->setClock(7);
$g->setClock(14);
$g->setClock(21);
|
Stateパターンを使用した例 State.pm package State {
use MorningState;
use DayState;
use NightState;
sub new {
my ($class) = @_;
return bless {}, $class;
}
sub clock {
my ($this, $context, $hour) = @_;
if ($hour >= 4 && $hour < 10) {
$context->changeState(MorningState::getInstance());
}
elsif ($hour >= 10 && $hour < 19) {
$context->changeState(DayState::getInstance());
}
elsif ($hour >= 20 || $hour < 4) {
$context->changeState(NightState::getInstance());
}
}
sub encounter {}
sub parting {}
}
1;
MorningState.pm package MorningState {
use feature 'state';
use base qw(State);
state $singleton = undef;
sub _new {
my $this = {};
return bless $this;
}
sub getInstance {
return $singleton ||= _new();
}
sub encounter {
my ($this, $context) = @_;
$context->encounter("おはよう");
}
sub parting {
my ($this, $context) = @_;
$context->parting("さようなら");
}
}
1;
DayState.pm package DayState {
use feature 'state';
use base qw(State);
state $singleton = undef;
sub _new {
my $this = {};
return bless $this;
}
sub getInstance {
return $singleton ||= _new();
}
sub encounter {
my ($this, $context) = @_;
$context->encounter("こんにちは");
}
sub parting {
my ($this, $context) = @_;
$context->parting("さようなら");
}
}
1;
NightState.pm package NightState {
use feature 'state';
use base qw(State);
state $singleton = undef;
sub _new {
my $this = {};
return bless $this;
}
sub getInstance {
return $singleton ||= _new();
}
sub encounter {
my ($this, $context) = @_;
$context->encounter("こんばんは");
}
sub parting {
my ($this, $context) = @_;
$context->parting("おやすみ");
}
}
1;
IContext.pm package IContext {
setClock {}
changeState {}
encounter {}
parting {}
}
1;
Greeting.pm package Greeting {
use base qw(IContext);
use NightState;
sub new {
my ($class) = @_;
my $this = { state => undef };
bless $this, $class;
$this->{state} = NightState::getInstance();
return $this;
}
sub setClock {
my ($this, $hour) = @_;
print "${hour}時\n";
$this->{state}->clock($this, $hour);
$this->{state}->encounter($this);
$this->{state}->parting($this);
}
sub changeState {
my ($this, $state) = @_;
$this->{state} = $state;
}
sub encounter {
my ($this, $msg) = @_;
print "${msg}\n";
}
sub parting {
my ($this, $msg) = @_;
print "${msg}\n";
}
}
1;
Main.pl use lib qw(./);
use Greeting;
my $g = new Greeting();
$g->setClock(7);
$g->setClock(14);
$g->setClock(21);
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、state に MorningState の インスタンスがセットされ、Greeting の setClock で この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.rb class Greeting
def setClock(hour)
puts "#{hour}時"
if hour < 4 || hour >= 19 then
@state = 3
elsif hour < 10 then
@state = 1
else
@state = 2
end
puts encounter()
puts parting()
end
def encounter()
if @state == 1 then
return "おはよう"
elsif @state == 2
return "こんにちは"
else
return "こんばんは"
end
end
def parting()
if @state == 1 then
return "さようなら"
elsif @state == 2 then
return "さようなら"
else
return "おやすみ"
end
end
end
Main.rb require './Greeting'
g = Greeting.new()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
Stateパターンを使用した例 State.rb require './MorningState'
require './DayState'
require './NightState'
class State
def clock(context, hour)
if hour >= 4 && hour < 10 then
context.changeState(MorningState::getInstance())
elsif hour >= 10 && hour < 19 then
context.changeState(DayState::getInstance())
elsif (hour >= 20 || hour < 4 then
context.changeState(NightState::getInstance())
end
def encounter(context)
puts "encounter メソッドを定義してください。"
end
def parting(context)
puts "parting メソッドを定義してください。"
end
end
MorningState.rb class State
end
class MorningState < State
@@singleton = MorningState.new()
def self.getInstance()
return @@singleton
end
def encounter(context)
context.encounter("おはよう")
end
def parting(context)
context.parting("さようなら")
end
end
DayState.rb class State
end
class DayState < State
@@singleton = DayState.new()
def self.getInstance()
return @@singleton
end
def encounter(context)
context.encounter("こんにちは");
end
def parting(context)
context.parting("さようなら");
end
end
NightState.rb class State
def clock(context, hour)
end
end
class NightState < State
@@singleton = NightState.new()
def self.getInstance()
return @@singleton
end
def encounter(context) {
context.encounter("こんばんは")
end
def parting(context) {
context.parting("おやすみ")
end
end
IContext.rb class IContext
def setClock(hour)
puts "setClock メソッドを定義してください。"
end
def changeState(state)
puts "changeState メソッドを定義してください。"
end
def encounter(msg)
puts "encounter メソッドを定義してください。"
end
def parting(msg)
puts "parting メソッドを定義してください。"
end
end
Greeting.rb require './IContext'
require './NightState'
class Greeting < IContext
def initialize() {
super()
@state = NightState::getInstance()
end
def setClock(hour)
puts "#{hour}時"
@state.clock(self, hour)
@state.encounter(self)
@state.parting(self)
end
def changeState(state)
@state = state
end
def encounter(msg)
puts msg
end
def parting(msg)
puts msg
end
end
Main.rb require './Greeting'
g = Greeting.new()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (@state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.py class Greeting:
def setClock(self, hour):
print(f"{hour}時")
if hour < 4 or hour >= 19:
self.state = 3
elif hour < 10:
self.state = 1
else:
self.state = 2
print(self.encounter())
print(self.parting())
def encounter(self):
if self.state == 1:
return "おはよう"
elif self.state == 2:
return "こんにちは"
else:
return "こんばんは"
def parting(self):
if self.state == 1:
return "さようなら"
elif self.state == 2:
return "さようなら"
else:
return "おやすみ"
Main.py from Greeting import Greeting
g = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
Stateパターンを使用した例 State.py from abc import ABCMeta, abstractmethod
from MorningState import MorningState
from DayState import DayState
from NightState import NightState
class State(metaclass=ABCMeta):
def clock(self, context, hour):
if hour >= 4 and hour < 10:
context.changeState(MorningState.getInstance())
elif hour >= 10 and hour < 19:
context.changeState(DayState.getInstance())
elif hour >= 20 or hour < 4:
context.changeState(NightState.getInstance())
@abstractmethod
def encounter(self, context):
pass
@abstractmethod
def parting(self, context):
pass
MorningState.py class State:
pass
class MorningState(State):
__instance = None
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = cls()
return cls.__instance
def encounter(self, context):
context.encounter("おはよう")
def parting(self, context)
context.parting("さようなら")
DayState.py class State:
pass
class DayState(State):
__instance = None
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = cls()
return cls.__instance
def encounter(self, context):
context.encounter("こんにちは")
def parting(self, context)
context.parting("さようなら")
NightState.py class State:
def clock(self, context, hour):
pass
class NightState(State):
__instance = None
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = cls()
return cls.__instance
def encounter(self, context):
context.encounter("こんばんは")
def parting(self, context)
context.parting("おやすみ")
IContext.py from abc import ABCMeta, abstractmethod
class IContext(metaclass=ABCMeta):
@abstractmethod
def setClock(self, hour):
pass
@abstractmethod
def changeState(self, state):
pass
@abstractmethod
def encounter(self, msg):
pass
@abstractmethod
def parting(self, msg):
pass
Greeting.py from IContext import IContext
from NightState import NightState
class Greeting(IContext):
def __init__(self):
self.state = NightState.getInstance()
def setClock(self, hour):
print(f"{hour}時")
self.state.clock(self, hour)
self.state.encounter(self)
self.state.parting(self)
def changeState(self, state):
self.state = state
def encounter(self, msg):
print(msg)
def parting(self, msg):
print(msg)
Main.py from Greeting import Greeting
g = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (self.state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.php <?php
class Greeting {
private $state;
public function setClock($hour) {
print $hour + "時\n";
if ($hour < 4 || $hour >= 19) {
$this->state = 3;
}
else if ($hour < 10) {
$this->state = 1;
}
else {
$this->state = 2;
}
print $this->encounter() . "\n";
print $this->parting() . "\n";
}
public function encounter() {
if ($this->state == 1) {
return "おはよう";
}
else if ($this->state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
public function parting() {
if ($this->state == 1) {
return "さようなら";
}
else if ($this->state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
}
?>
Main.php <?php
require_once('Greeting.php');
$g = new Greeting();
$g->setClock(7);
$g->setClock(14);
$g->setClock(21);
?>
|
Stateパターンを使用した例 State.php <?php
abstract class State {
require_once('State.php');
require_once('MorningState.php');
require_once('DayState.php');
require_once('NightState.php');
public function clock($context, $hour) {
if ($hour >= 4 && $hour < 10)
$context->changeState(MorningState::getInstance());
else if ($hour >= 10 && $hour < 19)
$context->changeState(DayState::getInstance());
else if ($hour >= 20 || $hour < 4)
$context->changeState(NightState::getInstance());
}
public abstract function encounter($context);
public abstract function parting($context);
}
?>
MorningState.php <?php
require_once('State.php');
class MorningState extends State {
private static $singleton;
public static function getInstance() {
if (!isset(self::$singleton)) {
self::$singleton = new MorningState();
}
return self::$singleton;
}
public function encounter($context) {
$context->encounter("おはよう");
}
public function parting($context) {
$context->parting("さようなら");
}
}
?>
DayState.php <?php
require_once('State.php');
class DayState extends State {
private static $singleton;
public static function getInstance() {
if (!isset(self::$singleton)) {
self::$singleton = new DayState();
}
return self::$singleton;
}
public function encounter($context) {
$context->encounter("こんにちは");
}
public function parting($context) {
$context->parting("さようなら");
}
}
?>
NightState.php <?php
require_once('State.php');
class NightState extends State {
private static $singleton;
public static function getInstance() {
if (!isset(self::$singleton)) {
self::$singleton = new NightState();
}
return self::$singleton;
}
public function encounter($context) {
$context->encounter("こんばんは");
}
public function parting($context) {
$context->parting("おやすみ");
}
}
?>
IContext.php <?php
interface IContext {
public function setClock($hour);
public function changeState($state);
public function encounter($msg);
public function parting($msg);
}
?>
Greeting.php <?php
require_once('IContext.php');
require_once('NightState.php');
class Greeting implements IContext {
private $state;
public function __construct() {
$this->state = NightState::getInstance();
}
public function setClock($hour) {
print $hour . "時\n";
$this->state->clock($this, $hour);
$this->state->encounter($this);
$this->state->parting($this);
}
public function changeState($state) {
$this->state = $state;
}
public function encounter($msg) {
print $msg . "\n";
}
public function parting($msg) {
print $msg . "\n";
}
}
?>
Main.php <?php
require_once('Greeting.php');
$g = new Greeting();
$g->setClock(7);
$g->setClock(14);
$g->setClock(21);
?>
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 ($this->state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.ts export
class Greeting {
private state:number;
public setClock(hour:number):void {
process.stdout.write(hour + "時\n");
if (hour < 4 || hour >= 19) {
this.state = 3;
}
else if (hour < 10) {
this.state = 1;
}
else {
this.state = 2;
}
process.stdout.write(encounter());
process.stdout.write(parting());
}
public encounter():string {
if (this.state == 1) {
return "おはよう";
}
else if (this.state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
public parting():string {
if (this.state == 1) {
return "さようなら";
}
else if (this.state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
}
Main.ts import {Greeting} from "./Greeting";
let g:Greeting = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
|
Stateパターンを使用した例 State.ts import {IContext} from "./IContext";
export
abstract class State {
public abstract clock(context:IContext, hour:number):void;
public abstract encounter(context:IContext):void;
public abstract parting(context:IContext);
}
MorningState.ts import {State} from "./State";
import {IContext} from "./IContext";
import {DayState} from "./DayState";
export
class MorningState extends State {
private static singleton:State = new MorningState();
public static getInstance():State {
return MorningState.singleton;
}
public clock(context:IContext, hour:number):void {
if (hour >= 4 && hour < 10)
context.changeState(MorningState.getInstance());
else
DayState.getInstance().clock(context, hour);
}
public encounter(context:IContext):void {
context.encounter("おはよう");
}
public parting(context:IContext):void {
context.parting("さようなら");
}
}
DayState.ts import {State} from "./State";
import {IContext} from "./IContext";
import {NightState} from "./NightState";
export
class DayState extends State {
private static singleton:State = new DayState();
public static getInstance():State {
return DayState.singleton;
}
public clock(context:IContext, hour:number):void {
if (hour >= 10 && hour < 19)
context.changeState(DayState.getInstance());
else
NightState.getInstance().clock(context, hour);
}
public encounter(context:IContext):void {
context.encounter("こんにちは");
}
public parting(context:IContext):void {
context.parting("さようなら");
}
}
NightState.ts import {State} from "./State";
import {IContext} from "./IContext";
export
class NightState extends State {
private static singleton:State = new NightState();
public static getInstance():State {
return NightState.singleton;
}
public clock(context:IContext, hour:number):void {
context.changeState(NightState.getInstance());
}
public encounter(context:IContext) {
context.encounter("こんばんは");
}
public parting(context:IContext) {
context.parting("おやすみ");
}
}
IContext.ts import {State} from "./State";
export
interface IContext {
setClock(hour:number):void;
changeState(state:State):void;
encounter(msg:string):void;
parting(msg:string):void;
}
Greeting.ts import {IContext} from "./IContext";
import {MorningState} from "./MorningState";
import {State} from "./State";
export
class Greeting implements IContext {
private state:State;
public setClock(hour:number):void {
this.state = MorningState.getInstance();
process.stdout.write(hour + "時");
this.state.clock(this, hour);
this.state.encounter(this);
this.state.parting(this);
}
public changeState(state:State):void {
this.state = state;
}
public encounter(msg:string):void {
process.stdout.write(msg + "\n");
}
public parting(msg:string):void {
process.stdout.write(msg + "\n");
}
}
Main.ts import {Greeting} from "./Greeting";
let g:Greeting= new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State を継承したクラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、state に MorningState の インスタンスがセットされ、Greeting の setClock で この例では、State クラスの各サブクラスに clock メソッドが存在していて、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲するというようにしています(Chain of Responsibilityパターン)。 ということは、サブクラスは、チェーンの次のサブクラスのことを知っている必要があるということです。 これは、将来 EveningState クラスを追加することになったら、DayState クラスも修正する必要があるということです。つまり、サブクラスに状態遷移をまかせてしまうと、サブクラス間の依存関係を深めてしまうことになります。 別の方法として、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 注意:State クラスでサブクラスを、サブクラスで State クラスをそれぞれ import すると、循環参照になり実行時にエラーになります。 |
|
Stateパターンを使用しない例 Greeting.swift public class Greeting {
private var state:Int = 0
public func setClock(_ hour:Int) {
print(String(hour) + "時")
if hour < 4 || hour >= 19 {
state = 3
}
else if hour < 10 {
state = 1
}
else {
state = 2
}
print(encounter())
print(parting())
}
public func encounter() -> String {
if state == 1 {
return "おはよう"
}
else if state == 2 {
return "こんにちは"
}
else {
return "こんばんは"
}
}
public func parting() -> String {
if state == 1 {
return "さようなら"
}
else if state == 2 {
return "さようなら"
}
else {
return "おやすみ"
}
}
}
Main.swift let g:Greeting = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
Stateパターンを使用した例 State.swift public class State {
public func clock(_ context:IContext, _ hour:Int) {
if hour >= 4 && hour < 10 {
context.changeState(MorningState.getInstance())
}
else if hour >= 10 && hour < 19 {
context.changeState(DayState.getInstance())
}
else if hour >= 20 || hour < 4 {
context.changeState(NightState.getInstance())
}
}
public func encounter(_ context:IContext) {
fatalError("calculateTotal メソッドを定義してください。")
}
public func parting(_ context:IContext) {
fatalError("calculateTotal メソッドを定義してください。")
}
}
MorningState.swift public class MorningState : State {
private static var singleton:MorningState = MorningState()
public static func getInstance() -> State {
return singleton
}
public override func encounter(_ context:IContext) {
context.encounter("おはよう")
}
public override func parting(_ context:IContext) {
context.parting("さようなら")
}
}
DayState.swift public class DayState : State {
private static var singleton:DayState = DayState()
public static func getInstance() -> State {
return singleton
}
public override func encounter(_ context:IContext) {
context.encounter("こんにちは")
}
public override func parting(_ context:IContext) {
context.parting("さようなら")
}
}
NightState.swift public class NightState : State {
private static var singleton:State = NightState()
public static func getInstance() -> State {
return singleton
}
public override func encounter(_ context:IContext) {
context.encounter("こんばんは")
}
public override func parting(_ context:IContext) {
context.parting("おやすみ")
}
}
IContext.swift public protocol IContext {
func setClock(_ hour:Int)
func changeState(_ state:State)
func encounter(_ msg:String)
func parting(_ msg:String)
}
Greeting.swift public class Greeting : IContext {
private var state:State = NightState.getInstance()
public func setClock(_ hour:Int) {
print(String(hour) + "時")
state.clock(self, hour)
state.encounter(self)
state.parting(self)
}
public func changeState(_ state:State) {
self.state = state
}
public func encounter(_ msg:String) {
print(msg)
}
public func parting(_ msg:String) {
print(msg)
}
}
Main.swift let g:Greeting = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.kt class Greeting {
private var state: Int = 0
fun setClock(hour: Int) {
println(hour.ToString() + "時")
state = if (hour < 4 || hour >= 19) {
3
}
else if (hour < 10) {
1
}
else {
2
}
println(encounter())
println(parting())
}
fun encounter(): String {
return if (state == 1) {
"おはよう"
}
else if (state == 2) {
"こんにちは"
}
else {
"こんばんは"
}
}
fun parting(): String {
return if (state == 1) {
"さようなら"
}
else if (state == 2) {
"さようなら"
}
else {
"おやすみ"
}
}
}
Main.kt fun main() {
val g = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
|
Stateパターンを使用した例 State.kt abstract class State {
fun clock(context: IContext, hour: Int) {
if (hour in 4..9) context.changeState(MorningState.getInstance())
else if (hour in 10..18) context.changeState(DayState.getInstance())
else if (hour >= 20 || hour < 4) context.changeState(NightState.getInstance())
}
abstract fun encounter(context: IContext)
abstract fun parting(context: IContext)
}
MorningState.kt class MorningState : State() {
companion object {
private val singleton = MorningState()
fun getInstance(): State {
return singleton
}
}
override fun encounter(context: IContext) {
context.encounter("おはよう")
}
override fun parting(context: IContext) {
context.parting("さようなら")
}
}
DayState.kt class DayState : State() {
companion object {
private val singleton = DayState()
fun getInstance(): State {
return singleton
}
}
override fun encounter(context: IContext) {
context.encounter("こんにちは")
}
override fun parting(context: IContext) {
context.parting("さようなら")
}
}
NightState.kt class NightState : State() {
companion object {
private val singleton = NightState()
fun getInstance(): State {
return singleton
}
}
override fun encounter(context: IContext) {
context.encounter("こんばんは")
}
override fun parting(context: IContext) {
context.parting("おやすみ")
}
}
IContext.kt interface IContext {
fun setClock(hour: Int)
fun changeState(state :State)
fun encounter(msg: String)
fun parting(msg: String)
}
Greeting.kt class Greeting : IContext {
private var state = NightState.getInstance()
override fun setClock(hour: Int) {
println(hour.toString() + "時")
state.clock(this, hour)
state.encounter(this)
state.parting(this)
}
override fun changeState(state: State) {
this.state = state
}
override fun encounter(msg: String) {
println(msg)
}
override fun parting(msg: String) {
println(msg)
}
}
Main.kt fun main() {
val g = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.scala class Greeting {
private var state: Int = 0
def setClock(hour: Int) {
System.out.println(hour + "時")
state = if (hour < 4 || hour >= 19) 3
else if (hour < 10) 1
else 2
System.out.println(encounter())
System.out.println(parting())
}
def encounter(): String =
if (state == 1) "おはよう"
else if (state == 2) "こんにちは"
else "こんばんは"
}
def parting(): String =
if (state == 1) "さようなら"
else if (state == 2) "さようなら"
else "おやすみ"
}
}
Main.scala object Main {
def main(args: Array[String]) {
val g = new Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
}
|
Stateパターンを使用した例 State.scala abstract class State {
def clock(context: IContext, hour: Int) {
if (hour >= 4 && hour < 10) context.changeState(MorningState.getInstance())
else if (hour >= 10 && hour < 19) context.changeState(DayState.getInstance())
else if (hour >= 20 || hour < 4) context.changeState(NightState.getInstance())
}
def encounter(context: IContext)
def parting(context: IContext)
}
MorningState.scala object MorningState {
private val singleton = new MorningState()
def getInstance(): State = singleton
}
class MorningState extends State {
def encounter(context: IContext) {
context.encounter("おはよう")
}
def parting(context: IContext) {
context.parting("さようなら")
}
}
DayState.scala object DayState {
private val singleton = new DayState()
def getInstance(): State = singleton
}
class DayState extends State {
def encounter(context: IContext) {
context.encounter("こんにちは")
}
def parting(context: IContext) {
context.parting("さようなら")
}
}
NightState.scala object NightState {
private val singleton = new NightState()
def getInstance(): State = singleton
}
class NightState extends State {
def encounter(context: IContext) {
context.encounter("こんばんは")
}
def parting(context: IContext) {
context.parting("おやすみ")
}
}
IContext.scala trait IContext {
def setClock(hour: Int)
def changeState(state :State)
def encounter(msg: String)
def parting(msg: String)
}
Greeting.scala class Greeting extends IContext {
private var state = NightState.getInstance()
def setClock(hour: Int) {
System.out.println(hour + "時")
state.clock(this, hour)
state.encounter(this)
state.parting(this)
}
def changeState(state: State) {
this.state = state
}
def encounter(msg: String) {
System.out.println(msg)
}
def parting(msg: String) {
System.out.println(msg)
}
}
Main.scala object Main {
def main(args: Array[String]) {
val g = Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.groovy class Greeting {
private int state
void setClock(int hour) {
System.out.println(hour + "時")
if (hour < 4 || hour >= 19) {
state = 3
}
else if (hour < 10) {
state = 1
}
else {
state = 2
}
System.out.println(encounter())
System.out.println(parting())
}
String encounter() {
if (state == 1) {
return "おはよう"
}
else if (state == 2) {
return "こんにちは"
}
else {
return "こんばんは"
}
}
String parting() {
if (state == 1) {
return "さようなら"
}
else if (state == 2) {
return "さようなら"
}
else {
return "おやすみ"
}
}
}
Main.groovy class Main {
static void main(String[] args) {
Greeting g = new Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
}
|
Stateパターンを使用した例 State.groovy abstract class State {
void clock(IContext context, int hour) {
if (hour >= 4 && hour < 10)
context.changeState(MorningState.getInstance())
else if (hour >= 10 && hour < 19)
context.changeState(DayState.getInstance())
else if (hour >= 20 || hour < 4)
context.changeState(NightState.getInstance())
}
abstract void encounter(IContext context)
abstract void parting(IContext context)
}
MorningState.groovy public class MorningState extends State {
private static MorningState singleton = new MorningState()
public static State getInstance() {
return singleton
}
public void encounter(IContext context) {
context.encounter("おはよう")
}
public void parting(IContext context) {
context.parting("さようなら")
}
}
DayState.groovy class DayState extends State {
private static DayState singleton = new DayState()
static State getInstance() {
return singleton
}
void encounter(IContext context) {
context.encounter("こんにちは")
}
void parting(IContext context) {
context.parting("さようなら")
}
}
NightState.groovy class NightState extends State {
private static NightState singleton = new NightState()
static State getInstance() {
return singleton
}
void encounter(IContext context) {
context.encounter("こんばんは")
}
void parting(IContext context) {
context.parting("おやすみ")
}
}
IContext.groovy interface IContext {
void setClock(int hour)
void changeState(State state)
void encounter(String msg)
void parting(String msg)
}
Greeting.groovy class Greeting implements IContext {
private State state = NightState.getInstance()
void setClock(int hour) {
System.out.println(hour + "時")
state.clock(this, hour)
state.encounter(this)
state.parting(this)
}
void changeState(State state) {
this.state = state
}
void encounter(String msg) {
System.out.println(msg)
}
void parting(String msg) {
System.out.println(msg)
}
}
Main.groovy class Main {
static void main(String[] args) {
Greeting g = new Greeting()
g.setClock(7)
g.setClock(14)
g.setClock(21)
}
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 Greeting.go import "fmt"
type Greeting struct {
state int
}
func (self *Greeting) SetClock(hour int) {
fmt.Printf("%d時\n", hour)
if hour < 4 || hour >= 19 {
self.state = 3
} else if hour < 10 {
self.state = 1
} else {
self.state = 2
}
fmt.Println(self.encounter())
fmt.Println(self.parting())
}
func (self *Greeting) encounter() string {
if state == 1 {
return "おはよう"
} else if state == 2 {
return "こんにちは"
} else {
return "こんばんは"
}
}
func (self *Greeting) parting() string {
if state == 1 {
return "さようなら"
} else if state == 2 {
return "さようなら"
} else {
return "おやすみ"
}
}
func NewGreeting() *Greeting {
return &Greeting{}
}
Main.go func main() {
var g = NewGreeting()
g.SetClock(7)
g.SetClock(14)
g.SetClock(21)
}
|
Stateパターンを使用した例 State.go import (
"fmt"
"os"
)
type IState interface {
clock(IContext, int)
getInstance() IState
encounter(IContext)
parting(IContext)
}
type State struct {
IState
}
func (self *State) clock(context IContext, hour int) {
if hour >= 4 && hour < 10 {
context.changeState(getMorningInstance())
} else if hour >= 10 && hour < 19 {
context.changeState(getMorningInstance())
} else {
context.changeState(getMorningInstance())
}
}
func (self *State) encounter(IContext) {
fmt.Fprintln(os.Stderr, "encounter メソッドを定義してください。")
}
func (self *State) parting(IContext) {
fmt.Fprintln(os.Stderr, "parting メソッドを定義してください。")
}
func NewState() *State {
return &State{}
}
MorningState.go type MorningState struct {
IState
}
var morningInstance = newMorningState()
func (self *MorningState) encounter(context IContext) {
context.encounter("おはよう")
}
func (self *MorningState) parting(context IContext) {
context.parting("さようなら")
}
func getMorningInstance() IState {
return morningInstance
}
func NewMorningState() IState {
var s IState
s = &MorningState {
IState: NewState(),
}
return s
}
DayState.go type DayState struct {
IState
}
var dayInstance = newDayState()
func (self *DayState) encounter(context IContext) {
context.encounter("こんにちは")
}
func (self *DayState) parting(context IContext) {
context.parting("さようなら")
}
func getDayInstance() IState {
return dayInstance
}
func NewDayState() IState {
var s IState
s = &DayState {
IState: NewState(),
}
return s
}
NightState.go type NightState struct {
IState
}
var nightInstance = newNightState()
func (self *NightState) encounter(context IContext) {
context.encounter("こんばんは")
}
func (self *NightState) parting(context IContext) {
context.parting("おやすみ")
}
func getNightInstance() IState {
return nightInstance
}
func NewNightState() IState {
var s IState
s = &NightState {
IState: NewState(),
}
return s
}
IContext.go type IContext interface {
SetClock(IContext, int)
changeState(IState)
encounter(string)
parting(string)
}
Greeting.go import "fmt"
type Greeting struct {
context IContext
}
var state = nightInstance
func (self *Greeting) SetClock(context IContext, hour int) {
fmt.Printf("%d時\n", hour)
state.clock(context, hour)
state.encounter(context)
state.parting(context)
}
func (self *Greeting) changeState(st IState) {
state = st
}
func (self *Greeting) encounter(msg string) {
fmt.Println(msg)
}
func (self *Greeting) parting(msg string) {
fmt.Println(msg)
}
func NewGreeting() *Greeting {
return &Greeting{}
}
Main.go func main() {
var g = NewGreeting()
g.SetClock(g, 7)
g.SetClock(g, 14)
g.SetClock(g, 21)
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State クラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、 (state に MorningState の インスタンスがセットされている) この例では、State クラスにclock メソッドが存在します。 ということは、State クラスは、サブクラスのことを知っている必要があるということです。 これは、将来 NightState クラスを削除することになったら、 State クラスも修正する必要があるということです。つまり、Stateクラスに状態遷移をまかせてしまうと、クラス間の依存関係を深めてしまうことになります。 別の方法として、この部分をChain of Responsibilityパターン(State の各サブクラスで、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲する)で記述すれば、変更しなければならない部分は減るでしょう。 また、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 |
|
Stateパターンを使用しない例 greeting.d public class Greeting {
private int state;
public void setClock(in int hour) {
printfln("%d時", hour);
if (hour < 4 || hour >= 19) {
state = 3;
}
else if (hour < 10) {
state = 1;
}
else {
state = 2;
}
printfln(encounter());
printfln(parting());
}
public string encounter() {
if (state == 1) {
return "おはよう";
}
else if (state == 2) {
return "こんにちは";
}
else {
return "こんばんは";
}
}
public String parting() {
if (state == 1) {
return "さようなら";
}
else if (state == 2) {
return "さようなら";
}
else {
return "おやすみ";
}
}
private void printfln(T...)(T args) { // Shift JIS 出力
import std, std.windows.charset, core.vararg;
std.stdio.write(to!(string)(toMBSz(std.format.format(args))) ~ "\n");
}
}
main.d import greeting;
int main() {
Greeting g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
return 0;
}
|
Stateパターンを使用した例 state.d import icontext;
public abstract class State {
public abstract void clock(IContext context, in int hour);
public abstract void encounter(IContext context);
public abstract void parting(IContext context);
}
morningState.d import state;
import icontext;
import daystate;
public class MorningState : State {
private static MorningState singleton;
public static this() {
singleton = new MorningState();
}
public static State getInstance() {
return singleton;
}
public override void clock(IContext context, in int hour) {
if (hour >= 4 && hour < 10)
context.changeState(MorningState.getInstance());
else
DayState.getInstance().clock(context, hour);
}
public override void encounter(IContext context) {
context.encounter("おはよう");
}
public override void parting(IContext context) {
context.parting("さようなら");
}
}
daystate.d import state;
import icontext;
import nightstate;
public class DayState : State {
private static DayState singleton;
public static this() {
singleton = new DayState();
}
public static State getInstance() {
return singleton;
}
public override void clock(IContext context, in int hour) {
if (hour >= 10 && hour < 19)
context.changeState(DayState.getInstance());
else
NightState.getInstance().clock(context, hour);
}
public void encounter(IContext context) {
context.encounter("こんにちは");
}
public void parting(IContext context) {
context.parting("さようなら");
}
}
nightstate.d import state;
import icontext;
public class NightState : State {
private static NightState singleton;
public static State getInstance() {
return singleton;
}
public static this() {
singleton = new NightState();
}
public override void clock(IContext context, in int hour) {
context.changeState(NightState.getInstance());
}
public void encounter(IContext context) {
context.encounter("こんばんは");
}
public void parting(IContext context) {
context.parting("おやすみ");
}
}
icontext.d import state;
public interface IContext {
public void setClock(in int hour);
public void changeState(State state);
public void encounter(in string msg);
public void parting(in string msg);
}
greeting.d import icontext;
import state;
import morningstate;
public class Greeting : IContext {
private State state;
public override void setClock(in int hour) {
state = MorningState.getInstance();
printfln("%d時", hour);
state.clock(this, hour);
state.encounter(this);
state.parting(this);
}
public override void changeState(State state) {
this.state = state;
}
public override void encounter(in string msg) {
printfln(msg);
}
public override void parting(in string msg) {
printfln(msg);
}
private void printfln(T...)(T args) { // Shift JIS 出力
import std, std.windows.charset, core.vararg;
std.stdio.write(to!(string)(toMBSz(std.format.format(args))) ~ "\n");
}
}
main.d import greeting;
int main() {
Greeting g = new Greeting();
g.setClock(7);
g.setClock(14);
g.setClock(21);
return 0;
}
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State を継承したクラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、state に MorningState の インスタンスがセットされ、Greeting の setClock で この例では、State クラスの各サブクラスに clock メソッドが存在していて、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲するというようにしています(Chain of Responsibilityパターン)。 ということは、サブクラスは、チェーンの次のサブクラスのことを知っている必要があるということです。 これは、将来 EveningState クラスを追加することになったら、DayState クラスも修正する必要があるということです。つまり、サブクラスに状態遷移をまかせてしまうと、サブクラス間の依存関係を深めてしまうことになります。 別の方法として、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 注意:State クラスでサブクラスを、サブクラスで State クラスをそれぞれ import すると、循環参照になり実行時にエラーになります。 |
|
Stateパターンを使用しない例 Greeting.pas unit UnitGreeting;
interface
uses
System.SysUtils;
type
Greeting = class
private
var state:integer;
public
procedure setClock(hour:integer);
function encounter():string;
function parting():string;
end;
implementation
procedure Greeting.setClock(hour:integer);
begin
Writeln(IntToStr(hour) + '時');
if (hour < 4) or (hour >= 19) then begin
state := 3;
end
else if hour < 10 then begin
state := 1;
end
else begin
state := 2;
end;
Writeln(encounter());
Writeln(parting());
end;
function Greeting.encounter():string;
begin
if state = 1 then begin
Result := 'おはよう';
end
else if state = 2 then begin
Result := 'こんにちは';
end
else begin
Result := 'こんばんは';
end;
end;
function Greeting.parting():string;
begin
if state = 1 then begin
Result := 'さようなら';
end
else if state = 2 then begin
Result := 'さようなら';
end
else begin
Result := 'おやすみ';
end;
end;
end.
Main.dpr program StateNon;
uses
System.SysUtils,
UnitGreeting;
var g:Greeting;
begin
g := Greeting.Create();
g.setClock(7);
g.setClock(14);
g.setClock(21);
g.Free;
end.
|
Stateパターンを使用した例 State.pas unit UnitState;
interface
uses
UnitIContext;
type
State = class
public
procedure clock(context:IContext; hour:integer); virtual; abstract;
procedure encounter(context:IContext); virtual; abstract;
procedure parting(context:IContext); virtual; abstract;
end;
implementation
end.
MorningState.pas unit UnitMorningState;
interface
uses
UnitState,
UnitIContext,
UnitDayState;
type
MorningState = class(State)
private
class var singleton:State;
class constructor Create();
public
class function getInstance():State; static;
procedure clock(context:IContext; hour:integer); override;
procedure encounter(context:IContext); override;
procedure parting(context:IContext); override;
end;
implementation
class constructor MorningState.Create();
begin
singleton := MorningState.Create();
end;
class function MorningState.getInstance():State;
begin
Result := singleton
end;
procedure MorningState.clock(context:IContext; hour:integer);
begin
if (hour >= 4) and (hour < 10) then begin
context.changeState(MorningState.getInstance());
end
else begin
DayState.getInstance().clock(context, hour);
end;
end;
procedure MorningState.encounter(context:IContext);
begin
context.encounter('おはよう');
end;
procedure MorningState.parting(context:IContext);
begin
context.parting('さようなら');
end;
end.
DayState.java unit UnitDayState;
interface
uses
UnitState,
UnitIContext,
UnitNightState;
type
DayState = class(State)
private
class var singleton:State;
class constructor Create();
public
class function getInstance():State; static;
procedure clock(context:IContext; hour:integer); override;
procedure encounter(context:IContext); override;
procedure parting(context:IContext); override;
end;
implementation
class constructor DayState.Create();
begin
singleton := DayState.Create();
end;
class function DayState.getInstance():State;
begin
Result := singleton
end;
procedure DayState.clock(context:IContext; hour:integer);
begin
if (hour >= 10) and (hour < 19) then begin
context.changeState(DayState.getInstance());
end
else begin
NightState.getInstance().clock(context, hour);
end;
end;
procedure DayState.encounter(context:IContext);
begin
context.encounter('こんにちは');
end;
procedure DayState.parting(context:IContext);
begin
context.parting('さようなら');
end;
end.
NightState.pas unit UnitNightState;
interface
uses
UnitState,
UnitIContext;
type
NightState = class(State)
private
class var singleton:State;
class constructor Create();
public
class function getInstance():State; static;
procedure clock(context:IContext; hour:integer); override;
procedure encounter(context:IContext); override;
procedure parting(context:IContext); override;
end;
implementation
class constructor NightState.Create();
begin
singleton := NightState.Create();
end;
class function NightState.getInstance():State;
begin
Result := singleton
end;
procedure NightState.clock(context:IContext; hour:integer);
begin
context.changeState(NightState.getInstance());
end;
procedure NightState.encounter(context:IContext);
begin
context.encounter('こんばんは');
end;
procedure NightState.parting(context:IContext);
begin
context.parting('おやすみ');
end;
end.
IContext.pas unit UnitIContext;
interface
type
IContext = interface
procedure setClock(hour:integer);
procedure changeState(state:TObject); // 循環参照の回避
procedure encounter(const msg:string);
procedure parting(const msg:string);
end;
implementation
end.
Greeting.pass unit UnitGreeting;
interface
uses
System.SysUtils,
System.StrUtils,
UnitIContext,
UnitState,
UnitMorningState;
type
Greeting = class(TInterfacedObject, IContext)
private
var _state:State;
public
procedure setClock(hour:integer);
procedure changeState(st:State);
procedure encounter(const msg:string);
procedure parting(const msg:string);
end;
implementation
procedure Greeting.setClock(hour:integer);
begin
_state := MorningState.getInstance();
Writeln(IntToStr(hour) + '時');
_state.clock(self, hour);
_state.encounter(self);
_state.parting(self);
end;
procedure Greeting.changeState(st:State);
begin
_state := st;
end;
procedure Greeting.encounter(const msg:string);
begin
Writeln(msg);
end;
procedure Greeting.parting(const msg:string);
begin
Writeln(msg);
end;
end.
Main.dpr program Main;
uses
System.SysUtils,
UnitGreeting,
UnitIContext;
var g:Greeting;
begin
g := Greeting.Create();
g.setClock(7);
g.setClock(14);
g.setClock(21);
g.Free;
end.
|
|
この例では、時刻によって state の状態を変え、挨拶を返すメソッドでその状態を if 文によって判定し、適当な文字列を返しています。出会ったときの挨拶を返すメソッド(encounter)にも if 文で分岐させて、分かれるときの挨拶を返すメソッド(parting)にも if 文で分岐させて、というように作ってあります。if 文で分岐させている箇所がいくつもありますが、一応動くので満足しています。 しかし、ここで、例えば(晩)という状態が増えたらどうでしょう。全部のメソッドの if 文を見直さなくてはならないことに、この段階になって気づくわけです。 このように、状態を追加する際に、全ての if 文の分岐を見直さなければならないような設計はあまり好ましいものではありません。 |
Stateパターンでは、機能でメソッドを分けるのではなく、状態で分けています。 この場合でも、もちろん時刻は見ていますが、State を継承したクラスだけです。状態が増えたり、状態の条件が変わったりすれば、変更しなければならないことは変わりませんが、状態を表すクラスだけですから変更のし忘れはないでしょう。 State パターンでは、「状態」を表すクラス、例であれば「朝の状態(MorningState)」「昼の状態(DayState)」 「夜の状態(NightState)」を用意し、この「状態」を入れ替え可能にしておきます。 そして、例えば、7時なら MorningState の インスタンスが、changeState というメソッドに引き渡され、そこで、_state に MorningState の インスタンスがセットされ、Greeting の setClock で この例では、State クラスの各サブクラスに clock メソッドが存在していて、自分の時間帯ならば処理するし、そうでなければ次の時間帯に処理を委譲するというようにしています(Chain of Responsibilityパターン)。 ということは、サブクラスは、チェーンの次のサブクラスのことを知っている必要があるということです。 これは、将来 EveningState クラスを追加することになったら、DayState クラスも修正する必要があるということです。つまり、サブクラスに状態遷移をまかせてしまうと、サブクラス間の依存関係を深めてしまうことになります。 別の方法として、状態遷移をGreeting クラスに任せてしまうこともできます。そうすれば、個々のStateのサブクラスの独立性が高まります。しかし、それではGreeting クラスがすべてのStateのサブクラスを知らなければならなくなります。この場合には、Mediatorパターンが適用できるかもしれません。 注意:State クラスでサブクラスを、サブクラスで State クラスをそれぞれ uses すると、循環参照になり実行時にエラーになります。 |