[RESOLVED] State pattern as a friend - A better alternative?

Started by
13 comments, last by rene_g 15 years, 11 months ago
Hi C++ experts! :-) I just realised that friendship is not inherited, which is causing me trouble. So I would really like some advice on maybe an alternative design, or how to workaround the friendship limitation. Here's the background: I have a class (Entity) which can change behavior. I implemented this with the state pattern. However, these states are extending the behavior of Entity, and therefore needs access to the private data of it. I thought this was possible by declaring my state base class a friend of Entity, but derived state classes do not inherit this friendship and therefore does not have access to Entity's private data. So, is there a way to work around this? Or should I consider another design? Thanks in advance! [Edited by - rene_g on May 4, 2008 6:09:49 PM]
Advertisement
Quote:
So, is there a way to work around this? Or should I consider another design?

In these situations, the first thing to do is always to convince somebody that there is a good reason why access to the private data is needed. Why can't that data be part of Entity's public interface? What is the nature of the changes that the state will make?

Convince me.
Okay, well, the state is responsible for user interaction with the entity. (It's a GUI) So Entity has functions like OnMouseButtonPress, OnMouseButtonRelease, OnMouseMove etc., which is all delegated to the current state object. Then it's the state that's responsible for the correct behavior. A correct behavior could be to change the Entity's state to another (From inactive to active fx.) or setting internal variables so the Entity can be moved, resized etc.

For the states to do their job effectively, they need access to implementation details, such as iterators on internal lists. And I don't believe it would be a good idea to expose that in the public interface.

I think of it like this: Entity is the representation of what is on the screen. But Entity has no behavior. The behavior is completely controlled by states. However, the states changes the representation, and acts on specifics of the representation. But noone else should have access to these details on the representation.
im having a bit of a hard time fully understanding ur structure... could u post the code?
Sure, here it is. I'm open to suggestions on how to improve the structure of my code, so feel free to comment on that. But please bear in mind, that right now my code is in a phase where I'm learning (Direct3D), and as such right now I've been more focused on getting things to work, than to plan ahead on the design.

Entity.h
#pragma once#include <list>#include <utility>using namespace std;#include "EntityState.h"#include "Controller.h"#include "GuiManager.h"#include "EntityObserver.h"#include "Observable.h"namespace GUI{	enum MouseButton;	class EntityState;	class Entity : public Observable, public EntityObserver	{	public:		Entity(Controller * controller);		virtual ~Entity();		bool IsVisible() const							{ return m_visible; }		float GetPositionX() const						{ return m_left; }		float GetPositionY() const						{ return m_bottom; }		float GetWidth() const							{ return m_width; }		float GetHeight() const							{ return m_height; }		unsigned int GetId() const						{ return m_id; }		Entity * GetParent() const						{ return m_parent; }		bool HasChildren() const						{ return !m_children.empty(); }		void SetVisible(bool visible)					{ m_visible = visible; }		void SetPosition(float x, float y)				{ m_left = x; m_bottom = y; }		void SetDimensions(float width, float height)	{ m_width = width; m_height = height; }		bool HitTest(float x, float y) const;		void AddChild(Entity * child);		bool RemoveChild(Entity * child);		void SetParent(Entity * parent);		void SetNewState(EntityState * newState);		D3DXMATRIX GetTransformMatrix();		Entity * HitTestChildren(float x, float y, bool putToFront);		pair<float, float> GetAbsolutePosition();		RECT GetScissorRect();		RECT GetParentScissorRect();		Entity * GetFirstChild() const;		void OnMouseMove(float x, float y);		void OnMouseButtonPress(float x, float y, MouseButton whichButton);		void OnMouseButtonRelease(float x, float y, MouseButton whichButton);		void OnKeyboardInput();		void OnLostFocus();		void Draw();	protected:		typedef list<Entity*> EntityList;		float m_left;		float m_bottom;		float m_width;		float m_height;		const float m_depth;		bool m_visible;		EntityState * m_state, * m_newState;		EntityList m_children;		Entity * m_parent;		Controller * m_controller;		D3DXMATRIX m_transform;		const unsigned int m_id;		EntityList::iterator FindChild(Entity * entityToFind);		inline void CheckState();		friend class EntityState;	};}


Snippet from Entity.h
namespace GUI{	void Entity::CheckState()	{		if (m_newState != 0)		{			delete m_state;			m_state = m_newState;			m_newState = 0;		}	}	void Entity::OnMouseMove(float x, float y)	{		CheckState();		if (m_state)			m_state->OnMouseMove(x, y);	}	void Entity::OnMouseButtonPress(float x, float y, MouseButton whichButton)	{		CheckState();		if (m_state)			m_state->OnMouseButtonPress(x, y, whichButton);	}	void Entity::OnMouseButtonRelease(float x, float y, MouseButton whichButton)	{		CheckState();		if (m_state)			m_state->OnMouseButtonRelease(x, y, whichButton);	}	void Entity::OnKeyboardInput()	{		CheckState();		if (m_state)			m_state->OnKeyboardInput();	}	void Entity::OnLostFocus()	{		CheckState();		if (m_state)			m_state->OnLostFocus();	}	void Entity::Draw()	{		CheckState();		if (m_state)			m_state->Draw();	}}


EntityState.h
#pragma once#include "Entity.h"namespace GUI{	enum MouseButton;	class Entity;	class EntityState	{	protected:		Entity & m_entity;	public:		EntityState(Entity & entity);		virtual void OnMouseMove(float x, float y);		virtual void OnMouseButtonPress(float x, float y, MouseButton whichButton);		virtual void OnMouseButtonRelease(float x, float y, MouseButton whichButton);		virtual void OnKeyboardInput();		virtual void OnLostFocus();		virtual void Draw();	};}


EntityState.cpp
#include "EntityState.h"namespace GUI{	EntityState::EntityState(Entity & entity) : m_entity(entity)	{		// Empty.	}	void EntityState::OnMouseMove(float x, float y)	{		Entity * affected = m_entity.HitTestChildren(x, y, false);		if (affected != 0)			affected->OnMouseMove(x - m_entity.GetPositionX(), y - m_entity.GetPositionY());	}	void EntityState::OnMouseButtonPress(float x, float y, MouseButton whichButton)	{		Entity * affected = m_entity.HitTestChildren(x, y, false);		if (affected != 0)			affected->OnMouseButtonPress(x - m_entity.GetPositionX(), y - m_entity.GetPositionY(), whichButton);	}	void EntityState::OnMouseButtonRelease(float x, float y, MouseButton whichButton)	{		Entity * affected = m_entity.GetFirstChild();		if (affected != 0)			affected->OnMouseButtonRelease(x - m_entity.GetPositionX(), y - m_entity.GetPositionY(), whichButton);	}	void EntityState::OnKeyboardInput()	{		Entity * affected = m_entity.GetFirstChild();		if (affected != 0)			affected->OnKeyboardInput();	}	void EntityState::OnLostFocus()	{		Entity * affected = m_entity.GetFirstChild();		if (affected != 0)			affected->OnLostFocus();	}	void EntityState::Draw()	{		Entity::EntityList::reverse_iterator iter = m_entity.m_children.rbegin();		for (; iter != m_entity.m_children.rend(); ++iter)		{			(*iter)->Draw();		}	}}


This is an example of implementing a simple window using Entity and EntityState
#pragma once#include "Entity.h"#include "EntityState.h"#include "Vertex3D.h"#include "GuiManager.h"#include "SimpleQuad.h"#include "Font.h"#include <string>#include <fstream>#include <utility>using namespace std;namespace GUI{	class Window : public Entity	{	public:		Window(Controller * controller) : Entity(controller) {}		void Update(GuiEvent eventType, unsigned int entityId);		void SetCaption(wstring newCaption) { m_caption = newCaption; }		wstring GetCaption() { return m_caption; }	protected:		wstring m_caption;	};	class State_WindowState : public EntityState	{	public:		State_WindowState(Entity & window) : EntityState(window)		{			m_quad.SetDevice(GuiManager::GetInstance()->GetDirect3DDevice());			m_quad.CalculateVertices(window.GetWidth(), window.GetHeight());			m_quad.SetColor(D3DCOLOR_XRGB(255, 0, 0));			m_quad.PrepareMesh();		}		void Draw();	protected:		SimpleQuad m_quad;	};	class State_WindowActive : public State_WindowState	{	public:		State_WindowActive(Entity & window) : State_WindowState(window)		{			m_quad.SetColor(D3DCOLOR_XRGB(200, 128, 128));			m_quad.PrepareMesh();		}		void OnMouseButtonPress(float x, float y, MouseButton whichButton);		void OnMouseMove(float x, float y);		void OnLostFocus();	};	class State_WindowInactive : public State_WindowState	{	public:		State_WindowInactive(Entity & window) : State_WindowState(window)		{			m_quad.SetColor(D3DCOLOR_XRGB(128, 128, 128));			m_quad.PrepareMesh();		}		void OnMouseButtonPress(float x, float y, MouseButton whichButton);	};	class State_WindowMove : public State_WindowState	{	protected:		float m_xRelative, m_yRelative;	public:		State_WindowMove(Entity & window) : State_WindowState(window)		{			m_xRelative = 0;			m_yRelative = 0;			m_quad.SetColor(D3DCOLOR_XRGB(128, 200, 128));			m_quad.PrepareMesh();		}		void OnMouseMove(float x, float y);		void OnMouseButtonRelease(float x, float y, MouseButton whichButton);		void SetRelativeCoordinates(float x, float y);	};	class WindowController : public Controller	{	protected:		Window * m_window, * m_childA, * m_childAA, * m_childB, * m_childBA;	public:		WindowController();		~WindowController();		void Update(GuiEvent eventType, unsigned int entityId);	};}


The implementation of the example:
#include "TestWindow.h"namespace GUI{	void Window::Update(GuiEvent eventType, unsigned int entityId)	{	}	// WindowState	//////////////	void State_WindowState::Draw()	{		RECT scissorRect = m_entity.GetParentScissorRect();		GuiManager::GetInstance()->GetDirect3DDevice()->SetScissorRect(&scissorRect);		m_quad.Draw(&m_entity.GetTransformMatrix());		EntityState::Draw(); // Draw children	}	// WindowActive	///////////////	void State_WindowActive::OnMouseButtonPress(float x, float y, MouseButton whichButton)	{		Entity * affected = m_entity.HitTestChildren(x, y, true);		if (affected != 0)			affected->OnMouseButtonPress(x - m_entity.GetPositionX(), y - m_entity.GetPositionY(), whichButton);		else		{			State_WindowMove * newState = new State_WindowMove(m_entity);			newState->SetRelativeCoordinates(x, y);			m_entity.SetNewState(newState);		}	}	void State_WindowActive::OnMouseMove(float x, float y)	{		if (m_entity.HitTest(x, y) && GuiManager::GetInstance()->GetMouseButtonPressed() != None)			m_entity.GetFirstChild()->OnMouseMove(x - m_entity.GetPositionX(), y - m_entity.GetPositionY());	}	void State_WindowActive::OnLostFocus()	{		State_WindowInactive * newState = new State_WindowInactive(m_entity);		m_entity.SetNewState(newState);	}	// WindowInactive	///////////////	void State_WindowInactive::OnMouseButtonPress(float x, float y, MouseButton whichButton)	{		Entity * affected = m_entity.HitTestChildren(x, y, true);		if (affected != 0)		{			affected->OnMouseButtonPress(x - m_entity.GetPositionX(), y - m_entity.GetPositionY(), whichButton);			State_WindowActive * newState = new State_WindowActive(m_entity);			m_entity.SetNewState(newState);			return;		}		else		{			State_WindowMove * newState = new State_WindowMove(m_entity);			newState->SetRelativeCoordinates(x, y);			m_entity.SetNewState(newState);			return;		}	}	// WindowMove	/////////////	void State_WindowMove::OnMouseMove(float x, float y)	{		m_entity.SetPosition(x - m_xRelative, y - m_yRelative);	}	void State_WindowMove::OnMouseButtonRelease(float x, float y, MouseButton whichButton)	{		m_entity.SetNewState(new State_WindowActive(m_entity));	}	void State_WindowMove::SetRelativeCoordinates(float x, float y)	{		m_xRelative = x - m_entity.GetPositionX();		m_yRelative = y - m_entity.GetPositionY();	}	// WindowController	///////////////////	WindowController::WindowController()	{		m_window = new Window(this);		m_window->SetDimensions(25.0f, 25.0f);		m_window->SetPosition(0.0f, 0.0f);		m_window->SetNewState(new State_WindowInactive(*m_window));		m_window->SetCaption(L"Parent");		m_childA = new Window(this); // Do not delete. m_window owns this and will delete it.		m_window->AddChild(m_childA);		m_childA->SetDimensions(10.0f, 10.0f);		m_childA->SetPosition(0.0f, 0.0f);		m_childA->SetNewState(new State_WindowInactive(*m_childA));		m_childA->SetCaption(L"Child A");		m_childAA = new Window(this); // Do not delete. m_window owns this and will delete it.		m_childA->AddChild(m_childAA);		m_childAA->SetDimensions(5.f, 5.f);		m_childAA->SetPosition(0.5f, 0.5f);		m_childAA->SetNewState(new State_WindowInactive(*m_childAA));		m_childAA->SetCaption(L"Child AB");		m_childB = new Window(this);		m_window->AddChild(m_childB);		m_childB->SetDimensions(10.0f, 10.0f);		m_childB->SetPosition(10.0f, 15.0f);		m_childB->SetNewState(new State_WindowInactive(*m_childB));		m_childB->SetCaption(L"Child B");		m_childBA = new Window(this);		m_childB->AddChild(m_childBA);		m_childBA->SetDimensions(5.0f, 5.0f);		m_childBA->SetPosition(0.5f, 0.5f);		m_childBA->SetNewState(new State_WindowInactive(*m_childBA));		m_childBA->SetCaption(L"Child BA");		GuiManager::GetInstance()->SetRootEntity(m_window);	}	WindowController::~WindowController()	{		if (m_window)			delete m_window;	}	void WindowController::Update(GuiEvent eventType, unsigned int entityId)	{	}}


Alot of code, I hope it's readable.
Hello Rene_G,

Quote:
I think of it like this: Entity is the representation of what is on the screen. But Entity has no behavior. The behavior is completely controlled by states. However, the states changes the representation, and acts on specifics of the representation. But noone else should have access to these details on the representation.


I can just about see where you are going with this, there are a number of ways this can be achieved:

1a) Move Entity's data into another class, say EntityDetails. Change Entity to contain an EntityDetails by value, then pass a reference to EntityDetails when delegating an event to the current state, i.e.

class EntityState{  ...  virtual void OnMouseMove(EntityDetails& entityDetails, float x, float y);  ...};


Now because you control EntityDetails by only passing it to EntityStates (and not providing any direct accessor from Entity) It would probably be reasonable to expose methods that access the (current) specific representation of your entity data. This will avoid the need for friend access and associated problem with EntityState subclasses.

As an extra point passing the EntityDetails into each method's call avoids the cyclic reference you get from storing a pointers between state <-> entity. And allows a single EntityState object to be shared by multiple Entities.

1b) You could achieve the same effect by using inheritance to separate EntityInterface and EntityImplementation, the interface contains your current Entity methods as pure virtuals and the implementation contains the your data, the current method implementations and methods needed to update your data.

2) Move the data EntityState.

3) Update EntityState to expose Entity data that its friend access allows, i.e.

BTW: I think this is really dodgy approach, but it does have the advantage of being the simplest change from you current code.

class EntityState{  ...  protected:     static float& ma_left( Entity& entiry ) { return entiry.ma_left; };  ...}


I'm sure there are many other approaches, my advice it to pick one try it and learn from any mistakes you later recognize. Personally I'd choose option 1a, but that's just me,

Cheers,

Tom
Just so you'd know, there's a slightly different way to implement TomH's first suggestion, described here.
Thank you Tom! That's a great suggestion! I think I will implement your first suggestion, as that really seems to fit well!

Thanks gage64, great link and interesting reading! I will also look into this!
Okay, maybe I was a bit too eager in my last post.

When thinking about how to implement your suggestion, Tom, I realised that it would lead to some other issues:

1. The Entity class is expected to be derived to a number of classes, Windows, Buttons, Labels etc. All these classes have data that are specific to them. So they would need specific EntityData classes. The Entity operates on the data, so it needs to know what kind of data it is using. I.e. a Button needs to know it's EntityData is a ButtonData. If I simply declare a base pointer in my Entity class to an EntityData, I'll need to cast it everytime I need to use it in Button's implementation. The alternative is not to declare a base pointer in Entity, but let every derived class of Entity declare its own pointer to a derived version of EntityData. Both alternatives doesn't feel like good design. :-/

2. The EntityState class acts on the specialised versions of EntityData, and so would either need to cast a base pointer to EntityData to a derived version, or only accept a derived version in the function parameter list. However, this means I need to reimplement my event delegation (OnMouseButtonPress etc) in the specialised versions of Entity, to ensure type safety, because the default implementation in Entity has no way of knowing what the base type pointer should be typecasted to. And besides, if I choose not to have a base pointer in Entity to EntityData, I can't supply a default implementation in Entity any way.

Does this make any sense?
Quote:Original post by rene_g
The Entity operates on the data, so it needs to know what kind of data it is using. I.e. a Button needs to know it's EntityData is a ButtonData.


Each derived class knows what data it needs and should therefore hold that data. Why should the base class hold data that's specific to the derived classes? It should only hold data that's common to all derived classes.

As a general rule, a base class should not need to know what classes derive from it, otherwise it violates the Open-Closed Principle.

This topic is closed to new replies.

Advertisement