Skip to main content
GameDev.net gamedev.net
🔒 Locked

Design Patterns

Started by T1Oracle Jun 15, 2006 at 5:32 AM 7 replies 1.4k views
Original Post
T1Oracle
T1Oracle
I love this book, (ISBN-0-201-63361-2) I've never thought about design in terms of "encapsulating variation" until reading this. Most of my question in how to properly design a system has been answer by the concepts presented in this book. Anyway, I know I haven't mastered them as I have not had the chance to even try them in C++ (although I did some stuff with &#106avascript using notepad :) ), but here are some of the patterns that I think are the most useful: Adapter - STL function adapters are definately useful, I can also think of many other applications. I also love the fact that it allows me more breathing room with interfaces. If the interface doesn't fit every dependant use, then an adapter can fix that. Bridge - still unsure on what this one is, but I once used a templated object to define the methods for accessing user input. That allowed me to decouple my input system from the version of Direct Input that I was relying on. To me that seems like a bridge, but that book's example is nothing like that. State - better way of implementing state machines. Seeing this leads me to believe that most uses of switch statements could redone using polymorphic objects that encapsulate the variations instead of using branching code. Factory/Abstract Factory - if I saw the code for something like this without the explanation I would've thought it was a stupid waste. However after reading I can imagine a few uses and it inspired a new idea for me that may eliminate the need for singletons. The idea which I came up with for a new design patter may be a "builder" however nothing in the book matches it. Regardless, programming a particle system for an arcade &#106avascript game that I made, (using notepad, I needed to keep my skills up :) ) brought me to the idea of a Particle Emitter (which came from here, suggested by a member of this forum). I realized that my interpretation of a Particle Emitter was a "Generator" (well, that's what I'll call it as it seems fair enough) of objects of the type "Particle". So here is what I define as the "Generator" pattern. Generator - Intent: Separate the initialization of objects which depend &#111;n the same set of shared data, from the process of creating them. Thus allowing instances to be created without the need of forwarding all of the initializing values to every location where these objects must be created. - Motivation: Particles within a particle system are intialized with a complex set of ranges (min velocity, max velocity, min x bound, max x bound) that determine how a given particle will behave. Furthermore, particles and many other types of objects often need reference to all other objects of their type within a given collection. This requires that the objects are instantiated with those values. Passing &#111;n this data manually in every location where these objects are created can become messy. It also tightens the coupling between the implementation of the object and its creation. By using a "Generator", the method of creating an object can be changed without having to alter implementation code everywhere else within the program. Example 1 code: Particle Emitter <!--STARTSCRIPT--><!--source lang="cpp"--><div class="source"><pre> <span class="cpp-keyword">class</span> Generator() { <span class="cpp-keyword">public</span>: Generator(<span class="cpp-keyword">int</span> minx_,<span class="cpp-keyword">int</span> maxx_,<span class="cpp-keyword">int</span> miny_,<span class="cpp-keyword">int</span> maxy_,<span class="cpp-keyword">int</span> minv_,<span class="cpp-keyword">int</span> maxv_) { minx=minx_; <span class="cpp-comment">// minimum x value</span> maxx=maxx_; <span class="cpp-comment">// maximum x value</span> miny=miny_; <span class="cpp-comment">// minimum y value</span> maxy=maxy_; <span class="cpp-comment">// maximum y value</span> minv=minv_; <span class="cpp-comment">// minimum velocity value</span> maxv=maxv_; <span class="cpp-comment">// maximum velocity value</span> } <span class="cpp-keyword">int</span> minx; <span class="cpp-comment">// minimum x value</span> <span class="cpp-keyword">int</span> maxx; <span class="cpp-comment">// maximum x value</span> <span class="cpp-keyword">int</span> miny; <span class="cpp-comment">// minimum y value</span> <span class="cpp-keyword">int</span> maxy; <span class="cpp-comment">// maximum y value</span> <span class="cpp-keyword">int</span> minv; <span class="cpp-comment">// minimum velocity value</span> <span class="cpp-keyword">int</span> maxv; <span class="cpp-comment">// maximum velocity value</span> Particle &amp;Generate() <span class="cpp-comment">// generates a new particle</span> {<span class="cpp-comment">// assume Rand(min,max) returns a random int between the values min and max</span> Particle p(Rand(minx,maxx),Rand(miny,maxy),Rand(minv,maxv),m_particles); <span class="cpp-keyword">return</span> p; } }; <span class="cpp-keyword">class</span> Particle() { <span class="cpp-keyword">public</span>: Particle(<span class="cpp-keyword">int</span> x,<span class="cpp-keyword">int</span> y,<span class="cpp-keyword">int</span> v); ... } <span class="cpp-keyword">int</span> main { Generator g(<span class="cpp-number">10</span>,<span class="cpp-number">100</span>, <span class="cpp-number">50</span>,<span class="cpp-number">500</span>, <span class="cpp-number">10</span>,<span class="cpp-number">15</span>); <span class="cpp-comment">// create new generator</span> ... <span class="cpp-comment">// to generate a new particle</span> Particle p=g.Generate(); ... } </pre></div><!--ENDSCRIPT--> Example 2 code: Texture Generator <!--STARTSCRIPT--><!--source lang="cpp"--><div class="source"><pre> <span class="cpp-keyword">class</span> Generator() { <span class="cpp-keyword">public</span>: std::map&lt;string,texture&gt; m_map; <span class="cpp-comment">// map of all textures by filename</span> Texture *Generate(string filename) <span class="cpp-comment">// generates a new texture</span> { Texture *t=&amp;m_map[filename]; <span class="cpp-keyword">if</span> (!t-&gt;isLoaded()) <span class="cpp-comment">// check if texture is loaded already</span> t-&gt;Load(filename); <span class="cpp-keyword">return</span> t; } }; <span class="cpp-keyword">int</span> main { Generator g(); <span class="cpp-comment">// create new generator</span> ... <span class="cpp-comment">// to generate a new texture or reference an existing one if that is already loaded</span> Texture *t=g.Generate(<span class="cpp-literal">"test.png"</span>); ... } </pre></div><!--ENDSCRIPT--> I would implement that last example with smart pointers, but it's just an example and I don't want to make it too complex. Anyway, I cannot wait until I'm back in America doing C++ and using design patterns.
Programming since 1995.
Stani R
Stani R
What exactly is the difference between your Generator and a "standard" Factory? I'm not exactly sure that there is a new "pattern" at work here. But that doesn't really matter - you should not feel yourself restricted by the GoF book in your programming, right? :) It's only a distillation of common programming practices, though it's useful for amateurs like us since we don't have to discover all this stuff the hard way.
snk_kid
snk_kid
I'm sorry to say that you haven't invented anything new, the word generator is already reserved for several different meanings depending on the context in the domain of programming.

If you where familiar with the C++ standard library containers & algorithms you would have already come cross one the meanings i.e the Generator Concept.


Furthermore your code has a serious flaw, returning a reference to a local variable with automatic storage duration.
T1Oracle
T1Oracle
Quote:
Original post by lightbringer
What exactly is the difference between your Generator and a "standard" Factory? I'm not exactly sure that there is a new "pattern" at work here. But that doesn't really matter - you should not feel yourself restricted by the GoF book in your programming, right? :) It's only a distillation of common programming practices, though it's useful for amateurs like us since we don't have to discover all this stuff the hard way.

The factory method in the book does not have any member variables. It also creates more than one kind of object. The factory method is used for creating a family of objects. My concept is for creating objects based on the same set of initilizing data.

As for the comment on my code returning a reference to variable with temporary storage... I agree, and I expect that there are other errors as well since I have no access to a compiler to work out the details. I was sure there was a practical and safe some way in C++ to create a new object and return it by refference without using pointers.

Anyway, I forgot two patterns that I also had a great appreciation for:

Decorator - beautiful concept, and it will probably be used in my scene graph as well as in my GUI.

Visitor - from this pattern I see the potential to outsource serialization to an external (visitor) object which could make serialization of complex trees of polymorphic heirarchal data easy. The one primary issue that would make that difficult is getting access to private data to serializers without destroying encapsulation. I imagine that I could use a friend class that can forward such access after being passed a refference to the class being accessed. Such a friend class could be passed on to a visitor thus preserving encapsulation. Of course that would greatly increase the amount of required classes, however it could allow for more operations beyond serialization visitors.

[Edited by - T1Oracle on June 16, 2006 3:55:57 AM]
Programming since 1995.
Sneftel
Sneftel
Quote:
Original post by T1Oracle
The factory method in the book does not have any member variables. It also creates more than one kind of object. The factory method is used for creating a family of objects. My concept is for creating objects based on the same set of initilizing data.

Choosing between subclasses is one method of customization a Factory has available to it. Passing in different initialization patterns is another. Don't pay too much attention to Factory not holding any data in their examples (which are not intended to exhaust all possibilities for each pattern). In practice, it's quite common for a Factory to hold private data detailing how to construct objects.

A very important concept to grasp in object oriented programming is that there is no fundamental difference between "objects of different subclasses" and "objects with different parameters". Here, I'll demonstrate.

class Adder{public:    virtual int AddTo(int i);};class ThreeAdder{public:    virtual int AddTo(int i) { return i+3; }};class FiveAdder{public:    virtual int AddTo(int i) { return i+5; }};class AdderFactory{public:    Adder* CreateAdder(int addend) { ... }}int main(){    AdderFactory af;    Adder* a1 = af.CreateAdder(5);    Adder* a2 = af.CreateAdder(3);}


class Adder(){public:    Adder(int addend) : m_addend(addend) { }    AddTo(int i) { return i+addend; }};class AdderFactory{public:    Adder* CreateAdder(int addend) { ... }}int main(){    AdderFactory af;    Adder* a1 = af.CreateAdder(5);    Adder* a2 = af.CreateAdder(3);}

What's the difference between these two setups (other than the contrived limitations of the first one)? Nothing. In each case, the returned Adder has been customized in some way. These two possibilities are hardly the only options for customization, by the way. Consider how the Strategy pattern could be applied here.
Emmanuel Deloget
Emmanuel Deloget
Quote:
Original post by T1Oracle
Quote:
Original post by lightbringer
What exactly is the difference between your Generator and a "standard" Factory? I'm not exactly sure that there is a new "pattern" at work here. But that doesn't really matter - you should not feel yourself restricted by the GoF book in your programming, right? :) It's only a distillation of common programming practices, though it's useful for amateurs like us since we don't have to discover all this stuff the hard way.

The factory method in the book does not have any member variables. It also creates more than one kind of object. The factory method is used for creating a family of objects. My concept is for creating objects based on the same set of initilizing data.

The fact that sometimes you have variables and sometimes you don't have them is an implementation detail - it change nothing to the actual abstraction.

Of course, the factory in the book is very simple - hiding the real purpose of the pattern with uneeded complexity would have been a bad thing. The same can be said for the book's singleton: it don't have any method but GetInstance().

So far, your pattern is a specialized example of the factory pattern. For example:
class TextureFactory{  std::map<std::string, Texture> mTextures;public:  Texture *create(const std::string& filename)  {    Texture *t = &mTextures[filename];    if (!t->isLoaded()) {      t->Load(filename);    }    return t;  }};

I just changed the name, and it is still a good name, isn't it?

Regards,

And while I don't support the war, I'd like to wish you good luck.
Stani R
Stani R
Quote:
Original post by T1Oracle
Visitor - from this pattern I see the potential to outsource serialization to an external (visitor) object which could make serialization of complex trees of polymorphic heirarchal data easy. The one primary issue that would make that difficult is getting access to private data to serializers without destroying encapsulation. I imagine that I could use a friend class that can forward such access after being passed a refference to the class being accessed. Such a friend class could be passed on to a visitor thus preserving encapsulation. Of course that would greatly increase the amount of required classes, however it could allow for more operations beyond serialization visitors.


It's no easier than a recursive call to a common base method, but it sure makes the distinction between data and logic cleaner. For private data, one thing you could probably do is pass it as an argument to the accept*() methods of your visitor.
ironfroggy
ironfroggy
Honestly, it looks like all you are doing is overcomplicating currying or partial function application. The examples in the book seem a little off to show you exactly what a factory can be, because they don't have to create more than one kind of object, of course, and they can definately include initialization data for the objects they create.

"Generator" is a different concept entirely, and I know it as an iterable object that generates its values on request with a co-routine kind of function, such as with Python.
(http://www.ironfroggy.com/)(http://www.ironfroggy.com/pinch)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.