Navigating Complexity: The Role of Trusted Partners and VIAS3D in Dassault Sy...
From code to pattern, part one
1. From Code to Pattern
Understanding classical GoF patterns with C++
2. Agenda
• Why this necessary
• OO fundamental
• OO in C++
• GoF classical pattern
• Creational
• Structural and behavioral
• Inter-operational
4:54:14 AM
2
3. Why this necessary
• Know what those terms mean on earth
• Know how they work
• Know why they came
• Know how to connect to your daily work
4:54:14 AM
3
5. OOD principles
• Program to an interface, not an implementation
• Favor object composition over class inheritance
• SOLID
• Single responsibility principle
• A class exist only for one reason
• Open/closed principle
• Add a new feature through extend exist class instead of modify it
• Liskov substitution principle
• In all cases, subclass can substitute its parent without invalidate any
desirable state
• Interface segregation principle
• Prefer specific interfaces than general purpose interface
• Dependency inversion principle
• Depends on abstract instead of high-level or low-level module
4:54:14 AM
5
6. How OOD works
• Finding Appropriate Objects
• Find the nouns in user case/scenario
• Determining Object Granularity
• Refine the noun if it can do complete unrelated things
• Specifying Object Interfaces
• Define what the object can do
• Specifying Object Implementations
• Define how the object does
4:54:14 AM
6
7. The core of OO
• Type and class
• Interface is the message it can handle
• Type is interface set
• Class is type implementation
• A class may have(support) more types
• Messaging
• The sender
• The receiver
• The message
• The message body
• Inheritance relationship
• “is-a”?
• “has-a”?
• “implement-a”?
• Abstraction
• Bind data and behaviors together
• Hide as more as possible information
4:54:14 AM
7
8. The core of OO in C++
• Type and class
• Interface is the message it can handle Virtual function in abstract class
• Type is interface set Abstract class
• Class is type implementation Concrete class
• A class may have(support) more types Multiple inheritance class
• Messaging
• The sender None or implicit object
• The receiver Named object
• The message Named member method
• The message body Parameters of member function
• Inheritance relationship
• “is-a”? Public inheritance from class
• “has-a”? Private inheritance or object composition
• “implement-a”? Public inheritance from type
• Abstraction
• Bind data and behaviors together Member variables and methods
• Hide as more as possible information Access modifier
4:54:14 AM
8
9. The simplest case study
• Set-and-call style
// client end code
void call() {
Foo foo;
foo.set_property("id", 100);
foo.set_property("name", "wooooo");
foo.bar();
}
• Try to identify the hardcode points
• Some questions
• How does an object get to create?
• How does an object get to destroy?
• How to send a message to an object?
• How does the object handle a message?
4:54:14 AM
9
10. How can it be flexible?
• Avoid hardcode! But what’s hardcode then?
• Data hardcode
• Hardcoded path, name, value etc.
• Should be avoided always and easy to do
• Structure hardcode
• Fixed class implementation
• Derivation relationship
• Use a class name directly
• Use a method name directly
• Add an extra layer
• Structure layer
• Interactive layer
• We get flexibility with the cost of complexity!
• Be sensitive with tradeoff
4:54:14 AM
10
11. Design pattern, the definition
• Wikipedia: “… is general reusable solution to a commonly occurring
problem in software design”
• GoF: “… names, abstracts, and identifies the key aspects of a
common design structure that make it useful for creating a reusable
object-oriented design”
• Essential of DP
• Specific name
• Scenario
• Solution
• We are going to:
• Show how simple codes are evolved and refactored to meet more
requirements and
• Show how GoF classify and name it
• NOTE
• The sample code does not care the delete of object. Just as object
creation, there are patterns to handle object lifecycle, such as RAII.
4:54:14 AM
11
13. How to get an object? I
• You know the class name
• Fully type information
available only at compile-time
• Stack based object
• Pros
• Straight
• Easy to use
• Map to language structure
directly
• Cons
• Compile-time limitation
• Scope based Lifecycle
• Hardcoded type
• Strict couple of server end
code and client end code
// server end code
struct Foo {
void bar() { cout << “bar calledn"; }
};
struct Foo2 {
void bar() { cout << “bar2 calledn"; }
};
void bang(Foo* foo, Foo2* foo2) {
foo->bar();
foo2->bar();
}
// client end code
void call() {
Foo foo;
Foo2 foo2
bang(&foo, &foo2);
}
4:54:14 AM
13
14. How to get an object? II
• Heap based object
• Pros
• Long life across scope
• Easy to use still
• Map to language structure
directly
• Cons
• Hardcoded type name
• Still tightly coupling
// server end code
struct Foo {
void bar() { cout << "bar calledn"; }
};
struct Foo2 {
void bar() { cout << "bar2 calledn"; }
};
void bang(Foo* foo, Foo2* foo2) {
foo->bar();
foo2->bar();
}
// client end code
Foo* get_foo() { return new Foo; }
Foo2* get_foo2() { return new Foo2; }
void call() {
Foo* foo = get_foo();
Foo2* foo2 = get_foo2();
bang(foo, foo2);
}
4:54:14 AM
14
15. How to get an object? III
• First try of refactor
• Abstract the interface
• Server end code cares about the
interface that Foo exposes
instead of Foo object
• Pros
• Decouple server end code so that
it’ll not depends on the Foo
definition
• Cons
• New derivation layer
• For any new implementation of
IFoo interface, client end code
must be modified
// server end code
struct IFoo {
virtual void bar() = 0;
};
void bang(const vector<IFoo*> vf) {
for_each(vf.begin(), vf.end(),
mem_fun(&IFoo::bar));
}
// client end code
struct Foo : IFoo {
virtual void bar() { cout << "bar calledn"; }
};
struct Foo2 : IFoo {
virtual void bar() { cout << "bar2 calledn"; }
};
IFoo* get_foo() { return new Foo; }
IFoo* get_foo2() { return new Foo2; }
void call() {
vector<IFoo*> vf;
vf.push_back(get_foo());
vf.push_back(get_foo2());
bang(vf);
}
4:54:14 AM
15
16. How to get an object? IV
• Second try of refactor
• Abstract the creation of object
• Decouple server end code and
client code completely
• Pros
• Easy to add new IFoo interface
implementation without
change server end and client
end code
• Server end code only define the
interfaces and the business
logics skeleton
• Cons
• Complex with double derivation
layers
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct IFooCreater {
virtual IFoo* create() = 0;
};
void bang(IFooCreater* fc) {
fc->create() ->bar();
}
struct Foo : IFoo {
virtual void bar() { cout << "bar calledn"; }
};
struct Foo2 : IFoo {
virtual void bar() { cout << "bar2 calledn"; }
};
struct FooCreater : IFooCreater {
enum FooType {TFoo, TFoo2} type_;
FooCreater(FooType t) : type_(t) {}
virtual IFoo* create() {
if (type_ == TFoo) return new Foo;
if (type_ == TFoo2) return new Foo2;
assert(0); return 0;
}
};
void call() {
bang(new FooCreater(FooCreater::TFoo));
bang(new FooCreater(FooCreater::TFoo2));
}
4:54:14 AM
16
17. The Factory Method
• What’s that? The Factory method!
• GoF: “Define an interface for creating an object, but let
subclasses decide which class to instantiate. Factory Method
lets a class defer instantiation to subclasses”
4:54:14 AM
17
18. How to get an object? I
• You have no class name, but
an existed object
• Copy constructor?
• Need to know the class of
the object
• RTTI?
• Not fully typed information
• There is no built-in support
in C++!
• The first try
• Seems useless at all, I know
it’s Foo here!
• An incorrect user scenario
// server end code
class Foo {
public: Foo* clone() {
return new Foo(*this);
}
};
vector<Foo*> foos;
void bang(Foo* f) {
Foo* baby = f->clone();
// change some attributes
foos.push_back(baby);
}
// client end code
static Foo default_foo;
void call() {
bang(&default_foo);
}
4:54:14 AM
18
19. How to get an object? II
• The second try
• Server end code should not
care about the class of the
object
• Pros
• Again, we move the concrete
implementation to client end
• Again, we can easily add new
// server end code
struct IFoo {
virtual void bar() = 0;
virtual IFoo* clone() = 0;
};
vector<IFoo*> foos;
void bang(IFoo* f) {
IFoo* baby = f->clone();
// change some attributes
foos.push_back(baby);
}
// client end code
class Foo : public IFoo {
virtual Foo* clone() {
return new Foo(*this);
}
virtual void bar() {}
};
static Foo default_foo;
void call() {
bang(&default_foo);
}
4:54:14 AM
19
20. The Prototype
• What’s that? The prototype!
• GoF: “Specify the kinds of objects to create using a prototypical
instance, and create new objects by copying this prototype”
• Dynamic languages don’t need this pattern as all type
information are available on runtime
4:54:14 AM
20
21. How to get the object? I
• In prototype sample, there is a default instance and all other
copies are cloned from it.
• And the instance should be global unique
• Should not be constructed by user
• Should be ready before the first use
• Should be cleanup elegance
• Had better never be constructed if it’s not be used
• So
1. Private constructor
2. Runtime library constructed instance (conflict with 1) or
runtime construct with condition
3. Resource management
4. Lazy-initialized
4:54:14 AM
21
22. How to get the object? II
• The initial implementation
• some issues
• IFoo implementation is bond
to server end and not easy
to extend
• Mixed functionalities of class
FooSingleton: it implements
both IFoo and singleton
interface
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct FooSingleton : public IFoo {
static IFoo* get_instance() {
if (!instance_)
instance_ = new FooSingleton;
return instance_;
}
void bar() { cout << "bar calledn"; }
private:
FooSingleton() {}
static IFoo* instance_;
};
IFoo* FooSingleton::instance_ = 0;
// client end code
void call() {
IFoo* foo = FooSingleton::get_instance();
foo->bar();
}
4:54:14 AM
22
23. How to get the object? III
• The final try
• Use another pattern we have
not discussed (the bridge
pattern) to avoid the cohesion
between interface and
implementation
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct IFooImpl {
virtual void bar_impl() = 0;
};
struct FooSingleton : public IFoo {
static IFoo* get_instance(IFooImpl* impl) {
if (!instance_)
instance_ = new FooSingleton(impl);
return instance_;
}
void bar() { impl_->bar_impl(); }
private:
FooSingleton(IFooImpl* i) : impl_(i) {}
IFooImpl* impl_;
static IFoo* instance_;
};
IFoo* FooSingleton::instance_ = 0;
// client end code
struct MyImpl : IFooImpl {
void bar_impl() { cout << "bar calledn"; }
};
void call() {
IFoo* foo = FooSingleton::get_instance(new MyImpl);
foo->bar();
}
4:54:14 AM
23
24. The Singleton
• What’s that? The singleton pattern!
• GoF: “Ensure a class only has one instance, and provide a
global point of access to it”
• More discussion on singleton
• Thread safety consideration
• Dependency among singletons
• Double-check idioms
• Return reference instead of pointer
4:54:14 AM
24
25. How to get some objects? I
• OK, we need some related or
dependent objects
• Same class?
• Stack based object array or
heap based dynamic object.
Works only if the Foo has a
default constructor.
// client end code
void same_class() {
Foo foos1[100];
Foo* foos2 = new Foo[100];
vector<Foo> foos3(100);
// cannot return foos1
// had better avoid return foos3
}
4:54:14 AM
25
//
// server end code, none
26. How to get some objects? II
• Different class but same
type?
• Construct directly
• Or use some factories
// server end code
strcut IFoo {
virtual void bar() = 0;
};
struct D1 : IFoo {
virtual void bar() {...}
};
struct D2 : IFoo {
virtual void bar() {...}
};
void bang(const vector<IFoo*>& fs) {
for_each(fs.begin(),
fs.end(),
mem_fun(&IFoo::bar));
}
// client end code
void call() {
vector<IFoo*> fs;
fs.push_back(new D1);
fs.push_back(new D2);
bang(fs);
}
4:54:14 AM
26
27. How to get some objects? III
• Different type?
• Cannot get a list of object
that have no shared type
• Instead, use a struct to warp
them and make each object
as a member and use them
by name
• Then, can you more specific?
• Say, type1 and type2 can be
subtyped both dynamically
…
struct package {
type1 v1;
type2 v2;
// …
};
void diff_type() {
package p;
// use(get/set) each member
// can return if necessary
}
4:54:14 AM
27
//
// server end code, none
28. How to get some objects? IV
• Let’s have a try
• Reconsider the scenario, we
are building a C language
compiler front end skeleton,
and it includes preprocessor
and assembler.
• How can we support
another language, say C++,
while will not affect the
server end code?
// server end code
struct front_end {
preprocesser* pp_;
assembler* as_;
void compile(string file) {
as_->doit(pp_->doit(file));
}
};
void do_compile(string file) {
front_end* fe = new front_end;
fe->pp_ = new preprocesser;
fe->as_ = new assembler;
fe->compile(file)
}
// client end code
void call() {
do_compile("hello.c");
}
4:54:14 AM
28
29. How to get some objects? V
• Refactor it so that it can
easy support more
language
• Abstract interface of
preprocess and assemble,
push the implementation
to client end.
// server end code
struct IPreprocesser {
virtual string doit(string f) = 0;
};
struct IAssemble {
virtual string doit(string f) = 0;
};
struct IFront_end_creator {
virtual IPreprocesser* create_pp() = 0;
virtual IAssemble* create_as() = 0;
};
class front_end {
IPreprocesser* pp_;
IAssemble* as_;
public:
front_end(IFront_end_creator* c) {
pp_ = c->create_pp();
as_ = c->create_as();
}
void compile(string file) {
as_->doit(pp_->doit(file));
}
};
4:54:14 AM
29
30. How to get some objects? VI
• The client code for C (right)
• The client code for C++ (below)
// client end code: C front end
struct CPreprocess : IPreprocesser {
virtual string doit(string f) { return f; }
};
struct CAssembale : IAssemble {
virtual string doit(string f) { return f; }
};
struct Cfe : IFront_end_creator {
virtual IPreprocesser* create_pp(){
return new CPreprocess;
}
virtual IAssemble* create_as() {
return new CAssembale;
}
};
// Client end code: C++ front end
struct CPPPreprocess : IPreprocesser {
virtual string doit(string f) { return f; }
};
struct CPPAssembale : IAssemble {
virtual string doit(string f) { return f; }
};
struct CPPfe : IFront_end_creator {
virtual IPreprocesser* create_pp() {
return new CPPPreprocess;
}
virtual IAssemble* create_as() {
return new CPPAssembale;
}
};
// client end code, how to use
void call() {
front_end cfe(new Cfe);
fe.compile("hello.c");
front_end cppfe(new CPPfe);
fe.compile("hello.cpp");
}
4:54:14 AM
30
31. The Abstract Factory
• A little complex, let’s summary what we did by now
• A derivation layer that create some related objects (IFront_end_creater)
• An object that depends on the creator to create objects of another derivation
layers (IPreprocesser and IAssemble)
• What’s this? The Abstract Factory!
• GoF: “Provide an interface for creating families of related or dependent
objects without specifying their concrete classes”
4:54:14 AM
31
32. How get an object from data? I
• Do you mean object serialization
and deserialization?
• Almost, but how?
• so we have ...
• Note, not clear boundary between
server and client.
• This is the simplest case and
cannot fit complex scenarios such
as the member is a point to some
type
• C++ has no reflect mechanism so
that we save type information to
file directly and then we can
construct an object from it directly
• Overriding works only if the
concrete object is ready, but we
have no yet. Chicken or eggs, eh?
struct record {
int id_;
string name_;
void serialize(ostream& os) {
os << id_;
os << name_ << endl;
}
void deserialize(istream& is) {
is >> id_;
is >> name_;
}
};
void call()
{
record r;
ifstream ifs("/root/data");
r.deserialize(ifs);
}
4:54:14 AM
32
33. How get an object from data? II
• Assume we are going to
serialize front_end object and
we only check the process of
deserializing.
• In order to get the class of
concrete object, we must save
the information as well as
object data. So
• each class derived from
serializer should have a static
global unique ID
• we should serialize the ID
before the object data
• The next question is: where
does the relationship between
ID and class exist?
• we need another helper type!
// server end code
struct serializer {
virtual void save(ostream& os) = 0;
virtual void load(istream& is) = 0;
};
struct IPreprocesser : serializer {
virtual string doit(string f) = 0;
};
struct IAssemble : serializer {
virtual string doit(string f) = 0;
};
class front_end : serializer {
IPreprocesser* pp_;
IAssemble* as_;
public:
virtual void save(ostream& os) {
pp_.save(os);
as_.save(os);
}
virtual void load(istream& is) {
// ??
}
};
4:54:14 AM
33
34. How get an object from data? III
• The help class will help construct
correct concrete object according
the object ID.
• Server end code first get the
object via helper and than load it
// server end code
struct front_end : serializer {
IPreprosseser* pp_;
IAssemble* as_;
ILoader* loader_;
virtual void save(ostream& os) {
os << pp_->id() << endl;
pp_->save(os);
os << as_->id() << endl;
as_->save(os);
}
virtual void load(istream& is) {
string pp_id, as_id;
is >> pp_id;
pp_ = loader_->get_pp(pp_id);
pp_->load(is);
is >> as_id;
as_ = loader_->get_as(as_id);
as_->load(is);
}
};
// server end code
struct serializer {
virtual string id() const = 0;
virtual void save(ostream&) = 0;
virtual void load(istream&) = 0;
};
struct IPreprosseser : serializer {};
struct IAssemble : serializer {};
struct ILoader {
virtual IPreprosseser*
get_pp(string) = 0;
virtual IAssemble*
get_as(string) = 0;
};
4:54:14 AM
34
35. How get an object from data? IV
• Client end provides the
implementation of the helper
class and specify different ID
for different class.
struct Front_end_loader : ILoader {
virtual IPreprosseser* get_pp(string id) {
if (id == CPreprocess::uuid)
return new CPreprocess;
else if (id == CPPPreprocess::uuid)
return new CPPPreprocess;
else throw "invalid preprocesser class id";
}
virtual IAssemble* get_as(string id) {
if (id == CAssemble::uuid)
return new CAssemble;
else if (id == CPPAssemble::uuid)
return new CPPAssemble;
else throw "invalid assemble class id";
}
};
// C front end implementation code
struct CPreprocess : IPreprocesser {
static string uuid;
virtual string id() const { return uuid; }
virtual void save(ostream& os) const {}
virtual void load(istream& is) {}
};
string CPreprocess::uuid = "CPreprocess";
struct CAssemble : IAssemble {
static string uuid;
virtual string id() const { return uuid; }
virtual void save(ostream& os) const {}
virtual void load(istream& is) {}
};
string CAssemble::uuid = "CAssemble";
void call()
{
const char* file = "C:test.dat";
Front_end_loader loader;
front_end fe(&loader);
bk.load(ifstream(file));
}
4:54:14 AM
35
36. The Builder
• We get running codes which satisfy following requirements
• Type based serialization/deserialization
• Easy to add new subtype without need modify the server end code
• Focus on the part of loading. What’s that? The Builder Pattern!
• GoF: “Separate the construction of a complex object from its
representation so that the same construction process can create
different representations”
4:54:14 AM
36
37. Sidebar: resource management
• We ignore the issue of resource management at all in previous
discussion
• There is no default GC support in C++, but we have an elegant
alternative, that’s RAII based resource management policy
• Put it simple, use std::shared_ptr or std::unique_ptr always for
all places that need a raw pointer
• For other non-pointer resource, such as kernel object handle,
locker, database connection, network connection etc., use RAII
plus reference counted wrapper if need.
• This is a big and serious topic and we need talk it separately.
4:54:14 AM
37
39. The object and its behaviors
• Object
• Object type. The message set the object accepts.
• Object class. The implementation of object types plus data that
represent object status.
• Object Id. The unique identifier of any object.
• For C++ language level object, it’s memory address of the object
• That is why objects of zero size class have different address
• Object behavior: how the object handle a message
• Object status decide the behaviors of it
• Those behaviors that do not depend on status are static
behaviors and will not be discussed here
• How to change an object?
• Set object status directly
• Call a r/w behavior of object
4:54:14 AM
39
40. Send a message to object
• Review C++ syntax of sending a message
• Null or implicit caller
• Directly depends on the detailed concrete object or a pointer to a type
• Hardcoded message name, the member function name
• Hardcoded massage property, the argument of the operation
• The message is taken action at once
• In C++, the sender is null (in global function, class static member
function) or implicitly (in class member function).
• Depends on the type implementation, C++ runtime will route the
message to correct concrete object.
• That’s polymorphism.
• C++ runtime only cares about the target object
• What if you want to routine the message by not only the type of target
but the type of source?
• That’s called double dispatch
• Switch/case? Be a respectful programmer and forget it!
4:54:14 AM
40
41. Double dispatch I
• Target uses C++ override mechanism
to distinguish different source class
and transfer the message back to
source, the first dispatch.
• Source uses C++ overload mechanism
to distinguish different target class,
the second dispatch.
• We have follow initial implementation
// server end code
struct Source;
struct Target { virtual void m(Source*) = 0; };
struct T1 : Target {virtual void m(Source* s); };
struct T2 : Target { virtual void m(Source* s); };
struct Source {
virtual void m(T1*) = 0;
virtual void m(T2*) = 0;
};
struct S1 : Source {
virtual void m(T1* t) { cout << "S1->T1n"; }
virtual void m(T2* t) { cout << "S1->T2n"; }
};
struct S2 : Source {
virtual void m(T1* t) { cout << "S2->T1n"; }
virtual void m(T2* t) { cout << "S2->T2n"; }
};
void T1::m(Source* s) { s->m(this); }
void T2::m(Source* s) { s->m(this); }
// client end code
void call()
{
Source* s1 = new S1;
Source* s2 = new S2;
Target* t1 = new T1;
Target* t2 = new T2;
t1->m(s1);
t1->m(s2);
t2->m(s1);
t2->m(s2);
}
4:54:14 AM
41
42. Double dispatch II
• Message m for target is only for exposing
itself so that source can do more. That is,
it accepts that object and transfer the
control back to the object, so we rename
the message as “accept”
• Message m for source is the actually place
we can handle different target type. It
provides all possible target type overloads
and visit them respectively, so we rename
the message as “visit”
• Meanwhile, the type source provides all
possible overloads of derived class of type
Target. That means it can walk the class
hierarchy of type Target. So we rename is
as “Visitor”
• Finally, we get following codes.
• What is it?
• It provides the possible that add new
operation to an existed class hierarchy.
• The Visitor class is strong bound to
Target class and any change of the Target
class hierarchy will cause the change of
Visitor interface.
// server end code
struct Visitor;
struct Target {
virtual void accept(Visitor*) = 0;
};
struct T1 : Target {
virtual void accept(Visitor* s);
};
struct T2 : Target {
virtual void accept(Visitor* s);
};
struct Visitor{
virtual void visit(T1*) = 0;
virtual void visit(T2*) = 0;
};
void T1::accept(Visitor* s) { s->visit(this); }
void T2::accept(Visitor* s) { s->visit(this); }
4:54:14 AM
42
43. The Visitor
• What’s this? It’s Visitor pattern!
• GoF: “Represent an operation to be performed on the elements of
an object structure. Visitor lets you define a new operation without
changing the classes of the elements on which it operates.”
4:54:14 AM
43
44. Send a message to an object
• Review C++ syntax of
sending a message
• Null or implicit caller
• Directly depends on the
detailed concrete object or
a pointer to a type
• Hardcoded message name,
the member function name
• Hardcoded massage
property, the argument of
the operation
• The message is taken action
at once
• How can it possible to erase
those?
4:54:14 AM
44
//serve end code
struct Target {
void foo() { cout << "Target::foo calledn"; }
};
// client end code
void call() {
Target t;
t.foo();
}
45. Indirect messaging I
• Use an indirect layer to
wrapper the message target
• Binding the message name
to the wrapped message
target
//serve end code
struct Target;
struct Wrapper {
typedef void (Target::*Action)();
Target* t_;
Action a_;
Wrapper(Target* t, Action a) : t_(t), a_(a) {}
void call() const { (t_->*a_)(); }
};
struct Target {
void foo() { cout << "Target::foo calledn"; }
};
// client end code
void call() {
Target t;
Wrapper w(&t, &Target::foo);
w.call();
}
4:54:14 AM
45
46. Indirect messaging II
• Try to refactor it
• Programming for interface
• Const correctness
• Use template to support
multiple concrete classes
// serve end code
struct IWrapper {
virtual void call() const= 0;
};
template <typename T>
struct Wrapper : IWrapper {
typedef void (T::*Action)();
T* t_;
Action a_;
Wrapper(T* t, Action a) : t_(t), a_(a) {}
virtual void call() const { (t_->*a_)(); }
};
// client end code
struct Target{
void foo() { cout << "Target::foo calledn"; }
} t;
struct Another {
void bar() { cout << "Another::foo calledn"; }
} a;
void call() {
vector<IWrapper*> vw;
vw.push_back(
new Wrapper<Target>(&t, &Target::foo));
vw.push_back(
new Wrapper<Target>(&a, &Another::foo));
for_each(vw.begin(), vw.end(),
mem_fun(&IWrapper::call));
}
4:54:14 AM
46
47. The Command
• What do we get? The Command pattern!
• GoF: “Encapsulate a request as an object, thereby letting you
parameterize clients with different requests, queue or log
requests, and support undoable operations”
• Question
• How to support undo with this pattern?
4:54:14 AM
47
48. Broadcast a message
• With native C++ support, we can only send one message to
one object once.
• How can we send a message to some objects?
• Loop on the object array/list/tree?
4:54:14 AM
48
49. Message broadcasting I
• In order to broadcast a
message, we need
• The message to be
broadcasted
• The target objects and they
must can handle the message
• An very native implementation
//serve end code
struct Target1 {
void foo() const { }
};
struct Target2 {
void foo() const { }
};
struct Broadcaster {
Target1 t1_;
Target1 t2_;
void broadcast() {
t1_.foo();
t2_.foo();
}
};
// client end code
void call() {
Broadcaster b;
b.broadcast();
}
4:54:14 AM
49
51. Message broadcasting III
• It’s possible that the
broadcaster itself will
broadcast something if its state
changes
• Provides subscribe &
unsubscribe interface
• Callback to all subscribers if
something occurs
//serve end code
struct ITarget {
virtual void foo() const = 0;
};
struct Broadcaster {
vector<ITarget*> ts_;
void broadcast() {
for_each(ts_.begin(), ts_.end(),
mem_fun(&ITarget::foo));
}
void subscribe(ITarget* t) {
ts_.push_back(t);
}
void unsubscribe(ITarget* t) {
ts_.remove(t);
}
};
4:54:14 AM
51
52. The Observer
• What’s that? The Observer pattern!
• GoF: “Define a one-to-many dependency between objects so
that when one object changes state, all its dependents are
notified and updated automatically”
4:54:14 AM
52
53. Send a message to someone
• OO always requires specific target, sometimes it’s rigorous
• The target is unknown on sending time (distributed environment)
• The sender does not care who will handle it
• There are maybe some policies to control what target will handle
the message (load balance)
• The target objects may have different priority to handle such
message
• How can it possible?
• All problems in computer science can be solved by another level
of indirection. So we need a gateway to get the message at first.
• Meanwhile, we also need routine the message to other if itself
cannot handle it, so we need chains them together
4:54:14 AM
53
54. Uncertain handler I
• First try
• Use a gateway class as the
intermediate layer
• Return if any handler
handles the message
• Some problems
• Strong relationship of
gateway class and concrete
handlers. Difficult to adjust
the order of handler
• Cannot represent the
relationship among
handlers, they are all at the
same level
// server end code
struct IFoo {
virtual void bar(int) const = 0;
virtual bool is_care(int) const = 0;
};
struct gateway : IFoo {
vector<IFoo*> fs_;
mutable bool handled_ = false;
virtual void bar(int n) const {
for (size_t i = 0; i < fs_.size(); ++i) {
if (fs_[i]->is_care(n)) {
fs_[i].bar(n);
break;
}
}
}
virtual bool is_care(int) const { return false; }
void add_handler(IFoo* i) { fs_.push_back(i); }
};
4:54:14 AM
54
56. Uncertain handler II
• Second try
• Using a link structure
• If one handler does not care
this message, it will routine
the message to its successor if
there is
// server end code
struct IFoo {
virtual void bar(int) const = 0;
};
struct IChainFoo : IFoo {
IFoo* next_;
explicit IChainFoo(IChainFoo* next = 0) :
next_(next) {}
virtual bool is_care(int n) const = 0;
virtual void bar_impl() const = 0;
virtual void bar(int n) const {
if (is_care(n))
bar_impl();
else if (next_)
next_->bar(n);
}
};
// client end code
// concrete handle implementation ignored
void call() {
IChainFoo* chain = new odd_handler(
new zero_handler(
new even_handler()));
chain->bar(7);
chain->bar(12);
chain->bar(0);
}
4:54:14 AM
56
57. The chain of responsibility
• What’s that of linked message handling? The chain of
responsibility pattern!
• GoF: “Avoid coupling the sender of a request to its receiver by
giving more than one object a chance to handle the request.
Chain the receiving objects and pass the request along the
chain until an object handles it”
4:54:14 AM
57
58. Load elephant into icebox I
• The very old story, how can you
load an elephant into a
refrigerator? Three steps!
• Open the door
• Load the elephant
• Close the door
• We have right initial draft
• Some comments
• Foo defines the steps by
implement bar() interface
while providing default
implementation (violate SRP)
• Difficult to extend
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct Foo : IFoo {
void open_refrigerator() {
cout << "open very big one ...n";
}
void load_elephant() {
cout << "load the elephant if it willn";
}
void close_refrigerator() {
cout << "close itn";
}
virtual void bar() {
open_refrigerator();
load_elephant();
close_refrigerator();
}
};
// client end code
void call() {
IFoo* foo = new Foo;
foo->bar();
}
4:54:14 AM
58
59. Load elephant into icebox II
• Refactor it to:
• Push the implementation to client
• Decouple the definition and
implementation
• So we have
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct IFooImpl {
virtual void open_refrigerator() = 0;
virtual void load_elephant() = 0;
virtual void close_refrigerator() = 0;
};
struct Foo : IFoo {
IFooImpl* impl_;
explicit Foo(IFooImpl* i) : impl_(i) {}
virtual void bar() {
open_refrigerator();
load_elephant();
close_refrigerator();
}
};
// client end code
struct MySolution : IFooImpl {
void open_refrigerator() {
cout << "open very big one ...n";
}
void load_elephant() {
cout << "load the elephant if it willn";
}
void close_refrigerator() {
cout << "close itn";
}
};
void call() {
IFoo* foo = new Foo(new MySolution);
foo->bar();
}
4:54:14 AM
59
60. The template method
• What’s that of base class defines skeleton while derived class
provides implementation? The template method pattern!
• GoF: “Define the skeleton of an algorithm in an operation, deferring
some steps to subclasses. Template Method lets subclasses redefine
certain steps of an algorithm without changing the algorithm's
structure”
• Our example is a little different with the default structure as we
push the implementation to client end
• Similar to Bridge pattern but with different intent.
4:54:14 AM
60
62. Handle message directly I
• As behaviors of an object are
decided by its state, so there
is a typical implementation.
• Possible issues
• Difficult to extend. If we
want to do something
special action for state 4, we
must modify the Foo
implementation
• Had better avoid
switch/case or if/else if/else
structure
• Indeed, an actually bug
there!
// server end code
struct Foo {
int state_;
void do_one() { cout << “action onen”; }
void do_two() { cout << “action twon”; }
void do_infinity() { cout << “action infinityn”; }
void set(int s) { state_ = s; }
void bar() {
switch(state_) {
case 1: do_one();
case 2: do_two();
default: do_infinity();
}
}
};
// client end code
void call() {
Foo foo;
foo.set(2);
foo.bar();
}
4:54:14 AM
62
63. Handle message directly II
• Refactor
• Server end codes are written
for interface only
• Push actually functional
codes to client end
• Avoid switch/case by strong
type
// server end code
struct IFoo_action {
virtual void do_action() = 0;
};
struct Foo {
IFoo_action* action_;
void set_action(IFoo_action* a) { action_ = a; }
void bar() { action_->do_action(); }
};
// client end code
struct Action_for_one : IFoo_action {
void do_action() { cout << "action onen"; }
};
struct Action_for_two : IFoo_action {
void do_action() { cout << "action twon"; }
};
struct Action_for_infinity : IFoo_action {
void do_action() { cout << "action infinityn"; }
};
// client end code
void call() {
Foo foo;
foo.set_action(new Action_for_two);
foo.bar();
}
4:54:14 AM
63
64. The strategy
• What’s that? The strategy pattern!
• GoF: “Define a family of algorithms, encapsulate each one,
and make them interchangeable. Strategy lets the algorithm
vary independently from clients that use it”
4:54:14 AM
64
65. Enhance message handling I
• There is an existed
implementation of some
types and you want to:
• Reuse the implementation
• Enhancement some specific
behaviors, such as log,
authentication, lock/unlock
etc
• The initial try is as right
• Drawbacks
• How about LSP?
• Difficult to extend
// server end code
struct IFoo {
virtual void bar() = 0;
virtual void baz() = 0;
};
struct Foo : IFoo {
void bar() { cout << "action barn"; }
void baz() { cout << "action bazn"; }
};
// client end code
void do_log(const char* msg) {
cout << msg << "n";
}
void call() {
Foo foo;
do_log("before call bar");
foo.bar();
}
4:54:14 AM
65
66. Enhance message handling II
• What does LSP say:
• Wiki : “if S is a subtype of T,
then objects of type T … may
be replaced with objects of
type S (i.e., objects of type S
maybe substituted for objects
of type T), without altering
any of the desirable
properties of that program
(correctness, task performed,
etc.)”
• Now we can treat the Foo
object and our customized
MyFoo object in the same
way
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct Foo : IFoo {
void bar() { cout << "action barn"; }
};
// client end code
void do_log(const char* msg) {
cout << msg << "n";
}
struct MyFoo : Foo {
void bar() {
do_log("before call bar");
Foo::bar();
}
};
void call() {
vector<IFoo*> vfs;
vfo.push_back(new Foo);
vfo.push_back(new MyFoo);
for_each(vfs.begin(), vfs.end(),
mem_fun(&IFoo::bar));
}
4:54:14 AM
66
67. The decorator
• What’s that? The decorator pattern!
• GoF: “Attach additional responsibilities to an object
dynamically. Decorators provide a flexible alternative to
subclassing for extending functionality”
4:54:14 AM
67
68. Delegated message handling
• Sometime, creating an
object has a big overhead or
just impossible
• A distributed object maybe
resident on another address
space, so we need RPC
infrastructure as the basis.
• You have the right to use it
only
• Creating and destroying such
object often is not desirable
4:54:14 AM
68
69. The proxy (TODO)
• What’s that? The proxy pattern!
• GoF: “Provide a surrogate or placeholder for another object to
control access to it”
• Remote proxy. For distributed OO system
• Virtual proxy. For lazy loading or initialization
• Protected proxy. For access control
• Smart pointer. For resource management or verification
4:54:14 AM
69
70. The state (TODO)
• What’s that? The state pattern!
• GoF: “Allow an object to alter its behavior when its internal
state changes. The object will appear to change its class”
4:54:14 AM
70
71. Separated object serialize (I)
• Image a ring buffer
implementation
• It gets store from client
• It manipulates the buffer
• Question: how to serialize the
ring buffer object?
• Answer one: serialize the buffer
also
• Bad solution. Client may have
done this. Duplication!
• Answer two: just serialize
front/back status
• Not good enough yet. After
deserialized, the object status
is not complete.
4:54:14 AM
71
// server end code
struct RingBuffer {
explicit RingBuffer(char* buff, size_t sz)
: _size(sz), _buff(buff), _front(0), _back(_buff)
{}
void push_back(char value) { … }
private:
size_t _size;
char* _buff;
char* _front
char* _back;
};
// client end code
void call() {
char buff[1024];
RingBuffer rb(buff, 1024);
const char* msg = “hello world!”;
std::copy(msg, msg + strlen(msg),
std::back_inserter<RingBuffer>(rb));
}
72. Separated object serialize (II)
• Answer three: delegate the
serialization task client fully
• Save internal status to a
Memo object
• Client can save this object
• Client can restore this object
later
• Will
4:54:14 AM
72
// server end code
struct RingBuffer {
explicit RingBuffer(char* buff, size_t sz) {…}
void push_back(char value) { … }
struct Memo {
size_t _sz;
size_t _off;
Memo(size_t sz, size_t off) : _sz(sz), _off(off) {}
};
Memo* create_memo() const {…}
void set_memo(Memo* m) {…}
};
73. The memento (TODO)
• What’s that? The memento pattern!
• GoF: “Without violating encapsulation, capture and
externalize an object's internal state so that the object can be
restored to this state later”
4:54:14 AM
73
75. The adapter
• What’s that? The adapter pattern!
• GoF: “Convert the interface of a class into another interface
clients expect. Adapter lets classes work together that
couldn't otherwise because of incompatible interfaces”
4:54:14 AM
75
76. Special object including I
• Many times, the object and its owner same type, that is,
they implement the same interface and can be used
interchangeable
• File system. A folder can includes folders and files and they are
all file system objects
• Windows system. A dialog may include some buttons, edit
boxes, combos etc. they are all windows indeed.
• Image following client codes
// client end code
void call() {
Dialog* dia = new Dialog("type your password");
dia->attach(new Button("OK"));
dia->attach(new Button("Cancel"));
dia->attach(new EditBox("Edit here"));
dia->enable();
}
4:54:14 AM
76
77. Special object including II
// server end code
struct IWin{
std::string name_;
virtual void enable() = 0;
// more windows properties
};
struct Button : IWin{
Button(const char* name) { name_ = name; }
void enable() { cout << "Button " << name_ << endl; }
};
struct EditBox : IWin{
EditBox(const char* name) { name_ = name; }
void enable() { cout << "EditBox " << name_ << endl; }
};
struct Dialog : IWin{
Dialog(const char* name) { name_ = name; }
void attach(IWin* w) { wins_.push_back(w); }
void enable() {
for_each(wins_.begin(), wins_.end(),
mem_fun(&IWin::enable));
cout << "Dialog "" << name_ <<"" enablen";
}
list<IWin*> wins_;
};
• And this is the server end
code
• All concrete classes
implement the same type
• If one of them to be a
container, it contains a
list of pointer to that type
• On message handling,
before/after its own
handling mechanism, it’ll
forward the message to
all objects it contains
4:54:14 AM
77
78. The composite
• What’s that? The composite pattern!
• GoF: “Compose objects into tree structures to represent part-whole
hierarchies. Composite lets clients treat individual objects and
compositions of objects uniformly”
• The pattern is seldom used alone, some other pattern make it more
useful
• Visitor. Add extra functionality to the class struct
• Iterator. Enumerate and manipulate each subobjects
• …
4:54:14 AM
78
80. Implementation an interface I
• Simple and straight, C++
provides directly support
• There is a very big limitation
however
• The implementation is bond
to server end code and
change the implementation
will definitely change server
end code
• How can we remove it?
• Add new indirection layer
• Push responsibility to client
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct Foo : IFoo {
void bar() { cout << "IFoo implementedn"; }
};
// client end code
void call() {
Ifoo* foo = new Foo;
foo->bar();
}
4:54:14 AM
80
81. Implementation an interface II
• Distinguish the type for interface and
the type for implementation
• Client end will provides the concrete
implementation while server end
only care about the interface to
implementation
• C++ usually calls this as p-impl idiom
and use it to eliminate the strong
header including relationship
// server end code
struct IFoo {
virtual void bar() = 0;
};
struct IFooImpl {
virtual void bar_impl() = 0;
};
struct Foo : public IFoo {
IFooImpl* impl_;
Foo(IFooImpl* i) : impl_(i) {}
void bar() { impl_->bar_impl(); }
};
// client end code
struct MyImpl : public IFooImpl {
virtual void bar_impl() {
cout << "my IFoo implementationn"; }
};
struct MyImplv2 : public IFooImpl {
virtual void bar_impl() {
cout << "my IFoo implementation v2n"; }
};
void call() {
vector<IFoo*> vf;
vf.push_back(new Foo(new MyImpl));
vf.push_back(new Foo(new MyImplv2));
for_each(vf.begin(), vf.end(),
mem_fun(&IFoo::bar));
}
4:54:14 AM
81
82. The bridge
• What’s that? The bridge pattern!
• GoF: “Decouple an abstraction from its implementation so
that the two can vary independently”
4:54:14 AM
82
84. Using a package I
• Usually, we use some classes of
a package to do something
• Typical, it’s the case that
exporting some APIs for 3rd
part use
• Pitfalls
• Sometimes you may not want
expose the details of internal
interfaces of server end code
to client
• Meanwhile, you don’t want to
hide something that hard to
use correctly or secrets
• Client end code depends on
the internal components of
server end code, which make
it’s very difficult to change
server end implementation
// server end code
namespace domain {
struct Foo {
void do_foo() { cout << "Foo usedn"; }
};
struct Bar {
void do_bar() { cout << "Bar usedn"; }
};
struct Baz {
void do_baz() { cout << "Baz usedn"; }
};
}
// client end code
void call() {
domain::Foo f;
domain::Bar b;
domain::Baz z;
f.do_foo();
b.do_bar();
z.do_baz();
}
4:54:14 AM
84
85. Using a package II
• Refactor it
• Add another layer
• Provide a new interface and
standard the implementation of
such common used tasks
• The original interfaces may or
may not available to client codes,
depends on how your codes are
used by client you expect
• Client end code will not continue
care about how the interfaces
are implemented. They only use
them! Simply and easily.
// server end code
namespace domain {
struct Foo {
void do_foo() { cout << "Foo usedn"; }
};
struct Bar {
void do_bar() { cout << "Bar usedn"; }
};
struct Baz {
void do_baz() { cout << "Baz usedn"; }
};
struct FooBarBaz {
Foo f_;
Bar b_;
Baz z_;
void do_foobarbaz {
f.do_foo();
b.do_bar();
z.do_baz();
}
};
}
// client end code
void call() {
domain::FooBarBaz fbz;
fbz.do_foobarbaz ();
}
4:54:14 AM
85
86. The facade
• What’s that? The façade pattern!
• GoF: “Provide a unified interface to a set of interfaces in a
subsystem. Facade defines a higher-level interface that makes the
subsystem easier to use”
• This is typical design method of package level interface. On the view
of client end, the package it used is much like another objects while
delegate all messages to its internal constructs.
• Client end should always depend on the package interface instead of
its internal implementation. If fine granularity controlling is needed,
just export those necessary, wrapped subsystem interfaces.
4:54:14 AM
86
87. The mediator (TODO)
• What’s that? The mediator pattern!
• GoF: “Define an object that encapsulates how a set of objects
interact. Mediator promotes loose coupling by keeping objects
from referring to each other explicitly, and it lets you vary
their interaction independently”
4:54:14 AM
87
88. The iterator (TODO)
• What’s that? The iterator pattern!
• GoF: “Provide a way to access the elements of an aggregate
object sequentially without exposing its underlying
representation”
4:54:14 AM
88
91. The flyweight (TODO)
• GoF: “Use sharing to support large numbers of fine-grained
objects efficiently”
4:54:14 AM
91
92. The interpreter (TODO)
• GoF: “Given a language, define a represention for its grammar
along with an interpreter that uses the representation to
interpret sentences in the language”
4:54:14 AM
92