Hello,
so as suggested to me in a former thread where I already asked something about writing a game engine solid, I've actually just carried on writing some basic graphics capabilities. Now I've got things running and actually came back to refactor these and hopefully finally learn those principles. I've read a lot of articles multiple times about the topic of S.O.L.I.D and about each idiom, but then again I best learn by doing, so I've tried. I still got some more questions and things I want to make sure before I overcomplicate things, especially things
- Single responsibility: I actually have a hard time destinquishing what a single responsibility is, that I should have each class perform. From what I've got its nothing as wide as "handling textures" and nothing as close as "acessing a texture", while I've even seen people suggest you should basically make a lot of classes that, due to their completely isolated task, do often not have more than one or two methods. On the other hand, I'd rather prefer classes that at least are something more than a namespace function and some data, so I'd rather go with that single responsibility is when "a class performs on the same set of data". Is this a correct case of this pattern? What would you suggest, and why? And is there some help for people who have a hard time trying to (conceptually) seperate concerns?
- Dependency inversion principly. For this, I've started actually implementing "interfaces" (say, classes with (almost -> we'll get to that later) only pure virtual functions), that I use to implement the actual buisness logic. I'm still unsure whether I'm doing it right, since most sample impementation where eigther targeted towards java or especially not game programming. So let's look at what I did with my graphics libary. Here how effect and texture worked before:
#pragma once
#include "D3DEffectPool.h"
#include "D3DEffect.h"
#include "Device.h"
#include "Texture.h"
#include <string>
class Effect
{
public:
Effect(Device& device, D3DEffectPool& d3DEffectPool, const std::wstring& sFileName);
virtual ~Effect(void);
void SetTexture(LPCSTR lpName, const Texture* pTexture);
void SetMatrix(LPCSTR lpName, const D3DXMATRIX& matrix);
void SetFloat(LPCSTR lpName, float value);
void SetFloat3(LPCSTR lpName, float first, float second, float third);
void SetFloat4(LPCSTR lpName, float first, float second, float third, float fourth);
void SetTechnique(LPCSTR lpName);
void Begin(void) const;
void BeginPass(void) const;
void ApplyChanges(void) const;
void End(void) const;
void EndPass(void) const;
private:
D3DEffect* m_pD3DEffect;
};
class Texture
{
public:
Texture(D3DTexture* pTexture);
virtual ~Texture(void);
int GetWidth(void) const;
int GetHeight(void) const;
int GetRef(void) const;
void AddRef(void);
void Release(void);
D3DTexture* GetD3DTexture(void) const;
private:
D3DTexture* m_pTexture;
int m_Ref;
};
So obviously both those classes would both provide the "interface" as well as the implementation. They would be used directly at any part of the respective graphics pipeline, therefore e.g. effect would depend on texture, and so on. Now to resolve that, I tried this:
´#pragma once
#include <d3dx9.h> //todo: remove, implement custom matrices!!!
#include "ITexture.h"
#include "IResource.h"
class IEffect : public IResource
{
public:
virtual void SetTexture(LPCSTR lpName, const ITexture* pTexture) = 0;
virtual void SetMatrix(LPCSTR lpName, const D3DXMATRIX& matrix) = 0;
virtual void SetFloat(LPCSTR lpName, float value) = 0;
virtual void SetFloat3(LPCSTR lpName, float first, float second, float third) = 0;
virtual void SetFloat4(LPCSTR lpName, float first, float second, float third, float fourth) = 0;
virtual void SetTechnique(LPCSTR lpName) = 0;
virtual void Begin(void) const = 0;
virtual void ApplyChanges(void) const = 0;
virtual void End(void) const = 0;
};
#pragma once
#include "IResource.h"
class ITexture : public IResource
{
public:
virtual int GetWidth(void) const = 0;
virtual int GetHeight(void) const = 0;
};
#pragma once
class IResource
{
public:
int GetRef(void) const { return m_numRef; }
void AddRef(void) { m_numRef += 1; }
virtual void Release(void) = 0;
protected:
int m_numRef;
};
Now I have three interfaces, one implements the reference counting, the other two are those from whom I inherit my actual texture and effect classes from. Now my questions: Now obviously everywhere where I used to have a Texture or Effect object, I'd instead have a ITexture or IEffect pointer.
1. is this a correct approach at dependency inversion? I just want to be sure, so in case this is OK, a simple "yes, is how dependency inversion works", or other wise "no, it would rather go that way..." would be nice. Remember, we are talking about applying one/more programming idioms, its not about getting things working (for the few people who'd say "do whatever works").
2. In case this is a correct approach, when and where should I apply this? Should I really create an "interface" for each and every dependency I might have, if not, how do I destinquish between cases where it makes sense and where not?
3. What about shared code and member variables? I didn't find any common "policies" for c++, take a look for example at my IResource class, should I rather have interfaces shared data member like the "m_numRef" and implement some common logic (like the setter and getter here) or should I really depend on the actual implemented class(es) to handle everything?
4. Even though I only depend on the interfaces, I might need to determine the actual type of an class at some point. I've thought about it, but since everywhere I'd generally pass an interface class, I can't think of any solution that goes without having to cast the actual type out of something. Not to say that casting is bad, but I wonder if this is "good practice" too, or if there is an better option. Like here:
void D3DEffect::SetTexture(LPCSTR lpName, const ITexture* pTexture)
{
//todo -> -> is evil, maybe overthink low level abstraction layer?
if(!pTexture)
m_pD3DEffect->SetTexture(lpName, nullptr);
else
m_pD3DEffect->SetTexture(lpName, static_cast<const Texture*>(pTexture)->GetD3DTexture());
}
This is my D3DEffect - implementation of the IEffect interface. As the pure virtual function takes an ITexture object, but the LPD3DXEFFECT needs a LPDIRECT3DTEXTURE9, I must cast, since I need to extract this texture which is part of the "class D3DTexture : public ITexture" implemenatation. Is this ok, or is there some better/cleaner way to achieve this? One potentialy issue is that one can pass an OpenGLTexture or XNATexture-implemenatition to the D3DEffect, which would throw an error - is this something I just have to deal with? Granted this is probably a very special case since you won't need this type destinction in most other applications, but still, how should I handle this?
5. As you see, IEffect still depends on ITexture. I've never found one word written about the dependency between interfaces. I think this is somewhat obvious but I still want to ask: this is normal and that kind of dependency we tolerate/want to achieve, right?
So this is it with my questions for now. An answer to any of those would be greatly apprechiated, I'm sure a lot of you guys have experience with those patterns. Like mentioned one time, please don't tell me "do whatever works for you", since I already have anything working, and this is my personal "lesson" for learing solid, so I really want to get it right this time, before faulty, half-a**ed over-complicated applying some not well-understood pattern to my engine that won't increase my code anyhow.