- 浏览: 293766 次
文章分类
最新评论
-
feargod:
...
ActivityGroup的子activity响应back事件的顺序问题 -
hoarhoar:
谢谢你,终于解决了,我真是受够了,总是45秒钟,真是疯了。
youku 的广告必须要屏蔽 -
lilai:
...
youku 的广告必须要屏蔽 -
aijuans2:
...
youku 的广告必须要屏蔽 -
weiwo1978:
说的非常好,mark
SELECT语句执行的顺序
协作式Visitor: 基于模板来创建Visitor的技巧
http://www.artima.com/cppsource/cooperative_visitor.html
This article presents a flexible and efficient variation of the Visitor design pattern in C++ that accommodates arbitrary argument and return types.
The Visitor pattern provides a way to add new polymorphic behavior to a class hierarchy without changing the hierarchy. Visitor has another advantage over virtual functions in that it localizes behavior to the visitor class rather than spreading it across the hierarchy.
As the need for Visitor is discovered, a programmer has two choices:
- Create visitors by hand or
- Use template libraries such as Loki [4] to automate visitor creation.
Irrespective of what option the programmer chooses, the problem inherent to visitor soon crops up—visitor commits to too much too early.
The standard visitor implementation requires implementing a "bouncing" function in every visitable class that calls the appropriate Visit
method in the visitor. This function is generally called Accept
:
class Shape { public: // bouncing function virtual int Accept( ShapeVisitor& visitor ) { return visitor.Visit( *this ); // call ShapeVisitor::Visit( Shape& ) } }; class Circle : public Shape { public: virtual int Accept( ShapeVisitor& visitor ) { return visitor.Visit( *this ); // call ShapeVisitor::Visit( Circle& ) } };
The Accept
function is virtual and it invokes the appropriate Visit
method, which is also virtual. This implements a limited form of multiple dispatch called double-dispatch.
Since the Accept
function must be virtual, it cannot be a template—it must hard-code all the knowledge about visitation, the return type, and the number of arguments.
As a result, a print
method implemented as a visitor for the Shape
hierarchy above must return an int
while it might be better to return void
or the output stream itself. Visitor often forces the programmer to write useless return statements and then store the return values as member variables. All arguments must also be marshaled as member variables.
class ShapePrinter : public ShapeVisitor { std::ostream& output_; // marshall stream as member public: ShapePrinter( std;:ostream& o ) : output_( o ) { } virtual int Visit( Shape& shape ) { /* print shape */ return 0; } virtual int Visit( Circle& circle ) { /* print circle */ return 0; } };
The same operation expressed as a virtual function is cleaner:
class Shape { public: virtual void print( std::ostream& output ) { // print shape } }; class Circle : public Shape { public: virtual void print( std::ostream& output ) { // print circle } };
Once a hierarchy commits to a return type, it's pretty much stuck with it forever.
Another serious limitation is that while it is easy to const-qualify a virtual function, it is difficult to specify const/non-const visitors. Expressing virtual functions as visitors requires implementing the visitor pattern twice for a hierarchy— once each for the non const and const versions. It is impossible to implement const visitors and non-const visitors for the same hierarchy using the Loki library[2][4] .
The standard visitor also has a cyclic dependency problem: every class knows about the visitor which knows about all other classes in the hierarchy. This often causes recompilation of modules when a new class is added. There's a solution to this called Acyclic Visitor[1] that uses dynamic casts to overcome this problem but is slower than cyclic visitor.
Cooperative Visitor
Cooperative Visitor is a template-based technique for finding reasonable solutions to the above- mentioned problems. It allows:
- Flexibility of acyclic visitor with performance closer to cyclic visitor
- Easier to use, maintain and modify
- Better semantic names rather than "visit"
- Easy creation of const/non-const visitors
- Flexible return and argument types
- Dynamic configuration of visitation behavior
- Implementing a default handler
- Developing template code upon visitor
The print
example could be expressed using Cooperative Visitor as:
class ShapePrinter : public Visitor< const Shape, void > { std::ostream& output_; public: ShapePrinter( std::ostream& o ) : output_( o ) { Visits( *this, Seq< Shape, Circle >::Type(), PrintInvoker() ); } public: void print( const Shape& shape ) { /* print shape */ } void print( const Circle& circle ) { /* print circle */ } typedef VISIT_INVOKER( print ) PrintInvoker; };
ShapePrinter
decides the return type as well as the const-ness through template arguments to Visitor. The visit methods are not virtual and are called print. There is no cyclic dependency: Shape
and Circle
know nothing about ShapePrinter
. The stream could also be passed as an extra argument to print.
Internals
The technique uses following approach:
- Generate a unique tag ( integer value) for every visitable class in a hierarchy
- Provide a way for every visitable object to return its tag
- Build a "virtual function table" where:
-
Table [ Tag ] = &Visit( Class )
, where:- Where
Table
is the virtual table -
Tag
is a tag value -
Visit
is the visit handler (a thunk: details follow) -
Class
is the class that has integer value "tag"
- Where
-
- When a visit operation is requested on an object, the tag of the object is retrieved and used as index into the "vtable." Then the visit function is invoked with the object as the argument.
The remainder of this article takes an in-depth look at an implementation.
Generating tags
C++'s enumeration types generate unique integer values but require knowledge of the number of tags (classes), a coupling similar to cyclic dependency. Templates provide a clean solution.
For every hierarchy we can generate a tag counter to keep track of next available tag:
template< typename Base > struct TagCounter { static size_t s_counter; // declaration } template< typename Base > size_t TagCounter< Base >::s_counter; // definition: default 0
For every visitable class, we can create a variable to store its tag:
template< typename Visitable, typename Base > struct TagHolder { static size_t s_tag; };
A tag is initialized the first time It is requested:
template< typename Visitable, typename Base > size_t GetTag() { size_t& tag = TagHolder< const Visitable, const Base >::s_tag; if( tag == 0 ) { // first time : generate tag tag = ++TagCounter< const Base >::s_counter; } return tag; }
The GetTag
templates applies const
qualifier to its arguments and so GetTag
and GetTag<const shape=""></const>
both return the same tag value. This prevents unnecessary tag generation and treats const/non-const objects in the same way.
To ensure initialization at startup, we force a call to GetTag
while defining the tag variable.
template< typename Visitable, typename Base > size_t TagHolder< Visitable, Base >:: s_tag = GetTag< Visitable, Base >();
This tag generation technique doesn't require class definitions of Visitable
and avoids cyclic dependencies. We cannot use local static variables in header files since a compiler could generate local static variables in every compilation unit.
Multi-threaded use of the tag generation technique during startup should be avoided or otherwise would require the counter variable would need to be wrapped using a synchronization primitive.
Tag retrieval
Curiously Recurring Template Pattern ( CRTP )[3] allows defining a helper function in Base
that every Visitable
class can use to retrieve its tag:
// Base class must inherit from BaseVisitable template< typename Base > struct VisitableBase { template< typename Visitable > size_t GetTagHelper( const Visitable* /* this */ ) const { return GetTag< Visitable, Base >(); } };
A macro helps defining a virtual function in every Visitable class to return its tag:
#define VIS_DEFINE_VISITABLE() / virtual size_t Tag() const / { / return GetTagHelper( this ); / }
Visitable
is an empty base class and the compiler could optimize it away. Here's how a client would look like (similar to Loki):
class Shape : public VisitableBase< Shape > { public: VIS_DEFINE_VISITABLE() }; class Circle : public Shape { public: VIS_DEFINE_VISITABLE() };
The VTable
The VTable
is parameterized over the type of function it holds as well as the Base
class.
template< typename Base, typename Func > class VTable { std::vector< Func > table_; // storage
A new function is added to the table by passing the Visitable
to find out the slot.
template< typename Visitable > void add( Func f ) { size_t index = GetTag< Visitable, Base >(); // find the slot if( index >= table_.size() ) { // find function for Base if it exists const size_t base_tag = GetTag< Base, Base >(); Func default_function = ( base_tag >= table_.size() ) ? 0 : table_[ base_tag ]; // expand the table table_.resize( index + 1, default_function ); } table_[ index ] = f; }
An indexing operation is also provided:
Func operator[] ( size_t index ) const { if( index >= table_.size() ) index = GetTag< Base, Base >(); return table_[ index ]; } }; // VTable
The VTable
defaults to function for Base
during expansion and retrieval resulting in a behavior closer to cyclic than acyclic visitor.
The Cooperative Visitor
Cooperative Visitor technique allows implementing visitors that take N arguments. For this discussion we look at a library implementation that allows creating visitors that take just one argument: the object being visited.
template< typename Base, typename ReturnType > class Visitor;
Clients derive from Visitor
and implement necessary visit methods:
class ShapeVisitor : public Visitor< Shape, double > { public: double Visit( Shape& ) { /* implementation */ } double Visit( Circle& ) { /* implementation */ } double Visit( Polygon& ) { /* implementation */ } };
Due to complexities involved in implementing single, multiple, and virtual inheritance, member functions of one class cannot be safely casted to member functions of another class.
So member functions of ShapeVisitor
must somehow be transformed into member functions of Visitor
so that they can be stored in the VTable
. This is accomplished using thunks.
Member function thunks
Thunks are small functions that map data from one form to another. They are often used to cast the this pointer and arguments before calling another function. Thunks are found in Loki Library (trampoline trick) and also used by compilers to implement virtual functions. We use thunks to generate functions in Visitor that calls visit functions in its derived classes:
template< typename Base, typename ReturnType > class Visitor { template< typename VisitorImpl, typename Visitable > ReturnType thunk( Base& b ) { // first dispatch: eg: cast to ShapeVisitor& VisitorImpl& visitor = static_cast< VisitorImpl& >( *this ); // second dispatch: eg: cast to Circle& Visitable& visitable = static_cast< Visitable& >( b ); // invoke visit method: eg: call ShapeVisitor::Visit( Circle& ) return visitor.Visit( visitable ); }
The thunk member template is a thunk generating engine and a thunk has the signature ReturnType (Visitor::*)( Base& )
. Since a thunk transforms actual Visit
methods defined in derived visitor implementation classes to member functions of the Visitor
class, it is a form of adapter. The thunks are stored in a statically created VTable
and each Visitor
instance holds a pointer to a vtable
.
typedef ReturnType (Visitor::*Func) ( Base& ); const VTable< const Base, Func >* vtable_; // vtable pointer
The function call operator implements the visitation. It retrieves the tag from the object and uses it as index to fetch the thunk from the vtable. Then it calls the thunk to initiate visitation:
ReturnType operator() ( Base& b ) { Func thunk = (*vtable_ )[ b.Tag() ]; // fetch thunk return ( this->*thunk ) ( b ); // pointer to member function syntax } }; // Visitor
Policy-based design
We can achieve name independence by using a policy class, Invoker
, that calls the visit method. This allows context based names for visit functions and is important in light of this excerpt: "the Visitor Pattern has nothing to do with visitation—the name was a real stumbling block for me" (Scott Meyers, My Most Important C++ Aha! Moments ..Ever[8]
template< typename VisitorImpl, typename Visitable, typename Invoker > ReturnType thunk( Base& b ) { VisitorImpl& visitor = static_cast< VisitorImpl& >( *this ); Visitable& visitable = static_cast< Visitable& >( b ); return Invoker::Invoke( visitor, visitable ); }
A Invoker
that uses print functions can be written as follows:
struct PrintInvoker { template< typename VisitorImpl, typename Visitable > static ReturnType Invoke(VisitorImpl& visitor, Visitable& visitable ) { check_member_function< ReturnType, VisitorImpl, Visitable > ( &Visitor::print ); // compile time assertion return visitor.print( visitable ); } };
check_member_function
is a compile-time assertion that would result in errors if the function VisitorImpl::print( Visitable& )
was not found. This prevents the compiler from silently choosing VisitorImpl::print( Base& )
. The print functions can return any type that is convertible to ReturnType
.
A macro can ease creating invokers :
#define VISIT_INVOKER( name ) / struct { / same code as print invoker but uses name / }
VISIT_INVOKER
creates an anonymous struct that clients could typedef
within their Visitor
implementations.
class ShapeRenderer : public Visitor< const Shape > { // const visitor public: void render( const Shape& ) { /* degenerate case */} void render( const Circle& ) { /* draw a circle */ } typedef VISIT_INVOKER( render ) RenderInvoker; };
Const-Visitors
Const-Visitors have visit methods of form ReturnType Visit( const Visitable& )
. The following template code expresses the relationship between Base
and Visitable
:
template< typename Visitable, typename Base > struct GetVisitMethodArgumentType { typedef Visitable Type; // ReturnType Visit( Visitable& ) }; // specialize for const Base template< typename Visitable, typename Base > struct GetVisitMethodArgumentType< Visitable, const Base > { typedef const Visitable Type; // ReturnType Visit( const Visitable& ) };
The thunk engine makes use of the above class template to perform either const or non-const visitation.
template< typename VisitorImpl, typename Visitable, typename Invoker > ReturnType thunk( Base& b ) { typdef typename GetVisitMethodArgumentType< Visitable, Base > ::Type VisitableType; VisitorImpl& visitor = static_cast< VisitorImpl& >( *this ); VisitableType & visitable = static_cast< VisitableType & >( b ); return Invoker::Invoke( visitor, visitable ); }
Visitor< Shape, int >
and Visitor< const Shape, Shape* >
are a non-const visitor returning int and a const visitor returning Shape*
respectively.
Creating VTables
Typelists are especially useful for automating table creation[2]. We use a minimalistic implementation borrowed from Loki library. The Seq
template helps creating typelists:
typedef Seq< Shape, Circle, Rect, Square >::Type VisitedList1; typedef Seq< Note, GraceNote >::Type VisitedList2;
In order to execute some code for every type in a typelist, we need to implement an "action class" with operator()
:
struct Action { template< typename T > void operator() ( const T* /* type hint */ ) { // do something for type T } };
The action can be applied for every type in a typelist:
apply( VisitedList1(), Action() ); // Shape, Circle, Rect, Square apply( VisitedList2(), Action() ); // Note, GraceNote
That's all we need to know about typelists to create vtables
. We implement a parameterized action class that creates vtables
:
template< typename Visitor, typename VisitedList, typename Invoker > struct CreateVtable { typename Visitor::VTable vtable_; /* vtable object */
The list of classes that need to be visited is represented by VisitedList
. For every class in the list, the appropriate thunk is instantiated and added to the vtable:
template< typename Visitable > void operator () ( const Visitable* /* hint */ ) { vtable_.template add< Visitable > ( // add to vtable &Visitor::template thunk < // instantiate thunk Visitor, Visitable, Invoker > ); }
The constructor adds the function for Base
first and then invokes apply to create the complete vtable with slots filled:
CreateVtable() { // add Base's visit function first ( *this ) ( static_cast< typename Visitor::Base* >( 0 ) ); // add visit function for each type in VisitedList apply( VisitedList(), *this ); } }; // CreateVtable
Vtables can be shared between Visitor
instances by creating static vtables. This can be achieved through static members of template classes:
template< typename Visitor, typename VisitedList, typename Invoker > struct GetStaticVtable { // declare static instanceof vtable static CreateVtable< Visitor, VisitedList, Invoker > s_table; // provide conversion operator operator const typename Visitor::VTable*() const { return &s_table.vtable_; } };
Type inference provided by functions is helpful for easing client usage:
// global helper function template< typename Visitor, typename VisitedList, typename Invoker > void Visits( Visitor& visitor, const VisitedList&, const Invoker& ) { // instantiate the static vtable and set the vtable pointer visitor.vtable_ = GetStaticVtable< Visitor, VisitedList, Invoker >(); }
A separate vtable is created for a combination of Visitor
, VisitedList
, Invoker
types.
The client needs to call Visits
in the constructors of the Visitor
s being implemented. This allows minimizing recompilation by moving the constructor body into cpp files. The alternative of passing the list of visited classes as another template parameter to Visitor
wouldn't allow this flexibility. Also for every list of passed as a template argument, a new Visitor
class would be generated, preventing Visitor
s that return the same type from having a common base class.
Putting it all together: examples
Here's how a client might implement a cloning const visitor for a shape hierarchy:
class Cloner : public Visitor< const Shape, Shape* > { public: Cloner() { // set the vtable pointer Visits( *this, Seq< Circle, Square, Rect >::Type(), CloneInvoker() ); } private: template< typename SomeShape > SomeShape * clone( const SomeShape& s) { return new SomeShape( s ); } typedef VISIT_INVOKER( clone ) CloneInvoker; }; void func( const Shape& s ) { Cloner clone; Shape* copy = clone( s ); // invoke () operator // do some stuff }
There's also ability to change behavior by choosing a different vtable during runtime:
class Renderer : public Visitor< const Shape, void > { public: Renderer( bool curved ) { if(curved ) Visits( *this, Seq< Circle, Bezier >::Type(), DrawInvoker() ); else Visits( *this, Seq< Square, Rect >::Type(), DrawInvoker() ); } void draw( const Shape& ); void draw( const Bezier& ); // other draw implementations typedef VISIT_INVOKER( draw ) DrawInvoker; };
The Visitor pattern is especially useful in compilers for manipulating syntax tree representations of a source program. Consider a syntax tree hierarchy:
class ASTree : public VisitableBase< ASTree > { VIS_DEFINE_VISITABLE() }; class Expression : public ASTree { VIS_DEFINE_VISITABLE() }; class UnaryExpression : public Expression { // expression formed by operators unary +, -, & etc VIS_DEFINE_VISITABLE() }; class BinaryExpression : public Expression { // expressions formed by binary arithmetic operators +, -, *, / etc // shift operators, assignment operators etc VIS_DEFINE_VISITABLE() }; class Statement : public ASTree { VIS_DEFINE_VISITABLE() }; class IfStatement : public Statement{ VIS_DEFINE_VISITABLE() }; class ForStatement : public Statement{ VIS_DEFINE_VISITABLE() };
A visitor is ideal for type checking syntax trees, pretty-printing, transforming etc. Cooperative Visitor is especially useful since some operation on trees are non modifying, others modifying; each operation has different return type, etc.
Here's how a type-checking visitor may be written in stages. A type checker also annotates the syntax tree with the type for each node in the tree and also returns the type of the node being processed:
// type check expressions class ExpressionChecker : public Visitor< ASTree, Type* > { public: typedef Seq< UnaryExpression, BinaryExpression >::Type VisitedList; ExpressionChecker() { Visits( *this, VisitedList(), TypeCheckInvoker () ); } Type* type_check( ASTree& ); Type* type_check( UnaryExpression& ); Type* type_check( BinaryExpression& ); typedef VISIT_INVOKER( type_check ) TypeCheckInvoker; }; // type check statements class StatementChecker : public ExpressionChecker { public: // list of typechecked statements typedef Seq< IfStatement, ForStatement >::Type List; // merge the lists typedef Append< List, ExpressionChecker::VisitedList >::Type VisitedList; StatementChecker() { Visits( *this, VisitedList(), TypeCheckInvoker () ); } Type* type_check( IfStatement& ); Type* type_check( ForStatement& ); // make Expressionchecker's methods visible using ExpressionChecker::type_check; };
It should be possible to automate building visitors in stages mimicking C++'s virtual table mechanism without the user having specifying the merged list of types being visited as above.
Future directions
Default vtable
When a visitor is constructed, unless Visits is invoked, the vtable_
pointer is null. Compile time techniques could be used to ensure that a visitor always has a valid vtable.
Casting
static_cast
won't work with virtual inheritance. Hence the thunk needs to handle casting to Visitable as a policy as well. This allows choice on a per class basis. Another alternative would be for the Tag
virtual function to return the tag as well as the this pointer (as void*
to prevent any implicit casting). A thunk would then take a void*
as argument and reinterpret_cast
it to appropriate Visitable
type.
template< typename VisitorImpl, typename Visitable, typename Invoker > ReturnType thunk( void* b ) // or const-void * { VisitorImpl& visitor = static_cast< VisitorImpl& >( *this ); VisitableType * visitable = reinterpret_cast< VisitableType* >( b ); return Invoker::Invoke( visitor, *visitable ); } ReturnType operator() ( Base& b ) { // fetch tag and this pointer std::pair< size_t, void* > tag_ptr = b.TagAndPtr(); Func thunk = (*vtable_ )[ tag_ptr.first ]; return ( this->*thunk ) ( tag_ptr.second ); }
Fast Visitor
Cooperative Visitor technique avoids the dynamic_cast
required by acyclic visitor yet provides a similar amount of flexibility. It's performance should be closer to that of cyclic visitor. A virtual function call could be eliminated if visitable classes stored the tags as a member rather than returning them from a virtual function (Tag)
.
DLLs
Windows DLLs present a problem: every DLL gets its own copy of static variables. This would result in the same class being associated with different tags in different DLLs. This problem can be solved by exporting tag generation functions from a single DLL and ensuring that each copy of tag variable for a class is initialized with the same tag value. This can be achieved without any change to the client usage model presented in the article. A complete discussion is beyond the scope of this article.
Ad-Hoc Visitor
All forms of visitor pattern require modifying the class hierarchy: the Accept
method or the Tag
method. In a hierarchy where such a change is not possible, typeid
could be used to provide a form of ad-hoc visitation[5]. The vtable would then be a map with type_info objects as keys and thunks as values. Ad-hoc visitation could be specified as a template argument to the visitor and much of other client usage should remain unchanged.
Custom Signature for Visit Methods
Variadic templates (C++0x) would be of great help in defining visitors that take N arguments. Having ability to pass extra arguments avoids having to implement such state as members of the Visitor.
Static Visit Methods
Static functions can be defined easily whereas once a class is defined one cannot add new member functions to it. Static visit methods would provide even more flexibility.
Instance vtables
Certain visitor instances could create and own their own vtables rather than using shared static vtables. This allows per object level dynamism but increases visitor construction cost.
Conclusions
Cooperative Visitor technique provides the flexibility of Acyclic visitor with performance that approaches the Cyclic visitor. Cooperative Visitor provides advantages over standard visitor implementations by allowing configuration: the name of visit methods, the return type, the const-ness, the number of arguments. The ease of use approaches that of other Visitor implementations and libraries. The technique is standard C++ and is portable.
Talk back!
Have an opinion about the Cooperative Visitor? Discuss this article in the Articles Forum topic, Cooperative Visitor: A Template Technique for Visitor Creation.
End notes
1. Robert Martin, Acylic Visitor presents a technique discussion of the Acyclic Visitor Pattern
http://www.objectmentor.com/resources/articles/acv.pdf
2. Andrei Alexandrescu. Modern C++ Design (Addison-Wesley Longman, 2001) is a good reference for generic programming, typelists, policy-based design, visitor pattern and the Loki Library.
3. James Coplien, Curiously Recurring Template Patterns. February 1995
4. Loki Library
http://loki-lib.sourceforge.net
5. Andrei Alexandrescu, Generic Programming: Typelists and Applications, Dr Dobbs Journal, 2003
http://www.ddj.com/dept/cpp/184403813;jsessionid=2AAAVOCJBEGSYQSNDLRCKH0 CJUNN2JVN?_requestid=1305388
6. Walter Bright. Backyard Hotrodding C++:, May 23, 2006, is another article which inspired the Cooperative Visitor technique
http://www.artima.com/cppsource/backyard.html
7. E.Gamma et al. Design Patterns ( Addison-Wesley Longman, 1995)
8. Scott Meyers, My Most Important C++ Aha! Moments, Ever September, 2006, discusses among other things, the counter intuitiveness of the Visitor pattern.
http://www.artima.com/cppsource/top_cpp_aha_moments.html
About the author
Anand Shankar Krishnamoorthi is an exploring C++ developer with 4 years of professional experience. His interests include C++ design, compilers, programming languages, performance analysis and optimization.
<script type="text/javascript"><!-- google_ad_client = "ca-pub-7104628658411459"; /* wide1 */ google_ad_slot = "8564482570"; google_ad_width = 728; google_ad_height = 90; //--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
相关推荐
《Recruit Restaurant Visitor Forecasting: 使用Jupyter Notebook进行数据分析与预测》 在现代商业环境中,数据分析和预测已经成为餐厅管理中的重要工具。"Recruit Restaurant Visitor Forecasting-master.zip" 是...
【标题】"visitor2002.github.io" 指的可能是一个个人或者组织在GitHub Pages上创建的静态网站。GitHub Pages是GitHub提供的一项服务,允许用户免费托管静态网页,通常用于个人博客、项目文档或者在线简历等。在这个...
笔记中列出了饿汉式、懒汉式、双重检测锁式、静态内部类和枚举类实现单例的方法,并探讨了防止反射和反序列化破解单例的方法。 2. 工厂模式(Factory Pattern):包括简单工厂模式、工厂方法模式和抽象工厂模式,...
行为型模式如策略(Strategy)、模板方法(Template Method)、观察者(Observer)、命令(Command)、迭代器(Iterator)、访问者(Visitor)、备忘录(Memento)、状态(State)、职责链(Chain of Responsibility...
- **Originator**:创建备忘录。 - **Caretaker**:保存备忘录。 ##### 1.3.7 观察者模式 - **定义**:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动...
例如,在一个基于Web的应用程序中,用户通过控制器提交表单,控制器调用模型处理数据,然后更新视图以显示结果。 2. **Mediator(中介者)模式**:该模式用于降低多个对象之间的复杂依赖关系,使得对象之间无需...
- **定义**:通过复制一个原型对象来创建新对象。 - **应用场景**:对象创建成本较高时。 - **实现方式**:通过实现`clone()`方法或使用序列化/反序列化。 #### 行为型模式 **6. Iterator(迭代器模式)** - **...
例如,单例模式通常使用懒汉式(lazy initialization)或饿汉式(eager initialization)来保证类的唯一实例;工厂方法模式则通过定义一个创建对象的接口,让子类决定实例化哪一个类;而装饰器模式允许动态地给对象...
包括策略模式(Strategy)、模板方法模式(Template Method)、命令模式(Command)、迭代器模式(Iterator)、访问者模式(Visitor)、责任链模式(Chain of Responsibility)、备忘录模式(Memento)、观察者模式...
设计模式通常分为三大类:创建型模式、结构型模式和行为型模式。每种模式都针对特定的编程问题,提供了可复用的解决方案。 1. 创建型模式:这类模式主要关注对象的创建过程,如单例模式(Singleton)、工厂方法模式...
- **Axure**:原型设计工具,用于创建交互式原型。 - **Visio**:流程图绘制工具,帮助清晰地表达项目流程。 - **MindManager**:思维导图软件,辅助进行项目规划和管理。 - **Photoshop**:图像处理软件,常...
设计模式分为三大类:创建型、结构型和行为型。创建型模式关注对象的创建,如单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)等,它们提供了创建对象的灵活方式,使得...
侯捷是台湾著名的IT教育家,他在2006年在元智大学资讯工程系开设的“Design Patterns 设计範式”课程,主要是教授设计模式知识。设计模式是一套被反复使用、多数人知晓、经过分类编目、代码设计经验的总结。使用设计...
3. 行为型模式:如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、命令模式(Command)、迭代器模式(Iterator)、访问者模式(Visitor)、责任链模式(Chain of Responsibility...
在书中,GoF列举了23种经典的设计模式,这些模式可以分为三大类:创建型模式、结构型模式和行为型模式。 1. 创建型模式(Creational Patterns):这类模式主要关注对象的创建过程,包括单例模式(Singleton)、工厂...
这些设计模式被分为三大类:创建型、结构型和行为型,每一类都旨在解决特定类型的软件设计问题。 ### 创建型模式 #### Factory模式 Factory模式是一种创建型设计模式,它提供了一个创建对象的接口,但允许子类决定...
行为型模式着重于对象之间的责任分配和通信,包括策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、命令模式(Command)、迭代器模式(Iterator)、访问者模式(Visitor)、责任链...
行为型模式则关注对象间的责任分配和交互,例如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、命令模式(Command)、迭代器模式(Iterator)、访问者模式(Visitor)、备忘录...
第17章 利用竞赛游戏来提升API设计技巧 300 17.1 概述 300 17.2 第一天 301 17.2.1 非public类带来的问题 304 17.2.2 不可变性带来的问题 304 17.2.3 遗漏实现的问题 308 17.2.4 返回结果可能不...