Introduction
在(原創) 我的Design Pattern之旅[6] : Adapter Pattern (OO) (Design Pattern) (C/C++) (.NET) (C#) (C++/CLI) (VB) 中的Grapher范例,我們看到Class Adapter必須針對Triangle、Circle、Square量身訂做TriangleDrawAdapter、CircleDrawAdapter、SquareDrawAdapter,雖然符合OCP,但每個class就得需要專屬的Adapter,會造成class爆炸,本文試著用泛型來解決此問題。
ISO C++
/**//*
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : DP_AdpaterPattern_Strategy_ClassByTemplate.cpp
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Template
Release : 07/11/2007 1.0
*/
#include <iostream>
using namespace std;
class IDrawStrategy {
public:
virtual void draw() const = 0;
};
class Grapher {
public:
Grapher(IDrawStrategy* drawStrategy = 0) : _drawStrategy(drawStrategy) {}
public:
void drawShape() const;
void setShape(IDrawStrategy* drawStrategy);
protected:
IDrawStrategy* _drawStrategy;
};
void Grapher::drawShape() const {
if (_drawStrategy)
_drawStrategy->draw();
}
void Grapher::setShape(IDrawStrategy* drawStrategy) {
_drawStrategy = drawStrategy;
}
class IPaint {
public:
virtual void paint() const = 0;
};
class Triangle : public IPaint {
public:
void paint() const;
};
void Triangle::paint() const {
cout << "Draw Triangle" << endl;
}
class Circle : public IPaint {
public:
void paint() const;
};
void Circle::paint() const {
cout << "Draw Circle" << endl;
}
class Square : public IPaint {
public:
void paint() const;
};
void Square::paint() const {
cout << "Draw Square" << endl;
}
template<typename T>
class DrawAdapter : public IDrawStrategy, private T {
public:
virtual void draw() const;
};
template<typename T>
void DrawAdapter<T>::draw() const {
paint();
}
int main() {
Grapher grapher(&DrawAdapter<Triangle>());
grapher.drawShape();
grapher.setShape(&DrawAdapter<Circle>());
grapher.drawShape();
grapher.setShape(&DrawAdapter<Square>());
grapher.drawShape();
}
執行結果
Draw Triangle
Draw Circle
Draw Square
從UML可以發現,TriangleDrawAdapter、CircleDrawAdapter和SquareDrawAdapter都不見了,只剩下一個DrawAdapter,為什麽可以這樣子呢?因為Class Adapter的致命傷就是得繼承Adaptee,這是很強的coupling,利用泛型,我們將繼承的class變成泛型的參數
70行
template<typename T>
class DrawAdapter : public IDrawStrategy, private T {
public:
virtual void draw() const;
};
這樣就可以在Client動態的以Adaptee為泛型參數傳入Class Adapter
int main() {
Grapher grapher(&DrawAdapter<Triangle>());
grapher.drawShape();
grapher.setShape(&DrawAdapter<Circle>());
grapher.drawShape();
grapher.setShape(&DrawAdapter<Square>());
grapher.drawShape();
}
C++/CLI
/**//*
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : DP_AdpaterPattern_Strategy_ClassByTemplate.cpp
Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Template
Release : 07/12/2007 1.0
*/
#include "stdafx.h"
using namespace System;
interface class IDrawStrategy {
void draw();
};
ref class Grapher {
public:
Grapher() : _drawStrategy(nullptr) {}
Grapher(IDrawStrategy^ drawStrategy) : _drawStrategy(drawStrategy) {}
public:
void drawShape();
void setShape(IDrawStrategy^ drawStrategy);
protected:
IDrawStrategy^ _drawStrategy;
};
void Grapher::drawShape() {
if (_drawStrategy != nullptr)
_drawStrategy->draw();
}
void Grapher::setShape(IDrawStrategy^ drawStrategy) {
_drawStrategy = drawStrategy;
}
interface class IPaint {
void paint();
};
ref class Triangle : public IPaint {
public:
virtual void paint();
};
void Triangle::paint() {
Console::WriteLine("Draw Triangle");
}
ref class Circle : public IPaint {
public:
virtual void paint();
};
void Circle::paint() {
Console::WriteLine("Draw Circle");
}
ref class Square : public IPaint {
public:
virtual void paint();
};
void Square::paint() {
Console::WriteLine("Draw Square");
}
template<typename T>
ref class DrawAdapter : public IDrawStrategy, public T {
public:
virtual void draw();
};
template<typename T>
void DrawAdapter<T>::draw() {
paint();
}
int main() {
Grapher^ grapher = gcnew Grapher(gcnew DrawAdapter<Triangle>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Circle>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Square>);
grapher->drawShape();
}
執行結果
Draw Triangle
Draw Circle
Draw Square
在.NET語言內,C++/CLI是唯一得天獨厚有兩種泛型的語言,C++/CLI可以用ISO C++既有的template,也可以用CLI的Generics。有了template,C++/CLI也可以實現這個技巧。
69行
template<typename T>
ref class DrawAdapter : public IDrawStrategy, public T {
public:
virtual void draw();
};
80行
int main() {
Grapher^ grapher = gcnew Grapher(gcnew DrawAdapter<Triangle>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Circle>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Square>);
grapher->drawShape();
}
C++/CLI也可以在Client動態改變泛型參數來改善Class Adapter。
看到這裡,你或許會問,C#可以嗎?既然C# 2.0也有泛型,答案很遺憾,目前C# 2.0不行,C# 3.0我還沒試,或許有興趣的朋友可以試看看。
C#
/**//*
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : DP_AdpaterPattern_Strategy_ClassByTemplate_Error.cs
Compiler : Visual Studio 2005 / C# 2.0
Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Template
Release : 07/11/2007 1.0
*/
using System;
interface IDrawStrategy {
void draw();
}
class Grapher {
protected IDrawStrategy _drawStrategy;
public Grapher() { }
public Grapher(IDrawStrategy drawStrategy) {
_drawStrategy = drawStrategy;
}
public void drawShape() {
if (_drawStrategy != null)
_drawStrategy.draw();
}
public void setShape(IDrawStrategy drawStrategy) {
_drawStrategy = drawStrategy;
}
}
interface IPaint {
void paint();
}
class Triangle : IPaint {
public void paint() {
Console.WriteLine("Draw Triangle");
}
}
class Circle : IPaint {
public void paint() {
Console.WriteLine("Draw Circle");
}
};
class Square : IPaint {
public void paint() {
Console.WriteLine("Draw Square");
}
}
// Error : Cannot derive from 'T' because it is a type parameter
class DrawAdapter<T> : IDrawStrategy, T {
public void draw() {
paint();
}
}
class Client {
static void Main() {
Grapher grapher = new Grapher(new DrawAdapter<Triangle>());
grapher.drawShape();
grapher.setShape(new DrawAdapter<Circle>());
grapher.drawShape();
grapher.setShape(new DrawAdapter<Square>());
grapher.drawShape();
}
}
以上C#無法compile成功,錯在55行
// Error : Cannot derive from 'T' because it is a type parameter
class DrawAdapter<T> : IDrawStrategy, T {
public void draw() {
paint();
}
}
錯誤訊息為
Error : Cannot derive from 'T' because it is a type parameter
也就是說,C#不允許繼承泛型,CLI的語言C#、VB、C++/CLI的Generics都無法用這個技巧,但C#卻可用『組合泛型』的方式完成。
C# by Generics
/**//*
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : DP_AdpaterPattern_Strategy_ClassByGenerics.cs
Compiler : Visual Studio 2005 / C# 2.0
Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Template
Release : 07/11/2007 1.0
*/
using System;
interface IDrawStrategy {
void draw();
}
class Grapher {
protected IDrawStrategy _drawStrategy;
public Grapher() { }
public Grapher(IDrawStrategy drawStrategy) {
_drawStrategy = drawStrategy;
}
public void drawShape() {
if (_drawStrategy != null)
_drawStrategy.draw();
}
public void setShape(IDrawStrategy drawStrategy) {
_drawStrategy = drawStrategy;
}
}
interface IPaint {
void paint();
}
class Triangle : IPaint {
public void paint() {
Console.WriteLine("Draw Triangle");
}
}
class Circle : IPaint {
public void paint() {
Console.WriteLine("Draw Circle");
}
};
class Square : IPaint {
public void paint() {
Console.WriteLine("Draw Square");
}
}
class DrawAdapter<T> : IDrawStrategy where T : IPaint, new() {
protected T _adaptee = new T();
public void draw() {
_adaptee.paint();
}
}
class Client {
static void Main() {
Grapher grapher = new Grapher(new DrawAdapter<Triangle>());
grapher.drawShape();
grapher.setShape(new DrawAdapter<Circle>());
grapher.drawShape();
grapher.setShape(new DrawAdapter<Square>());
grapher.drawShape();
}
}
執行結果
Draw Triangle
Draw Circle
Draw Square
55行
class DrawAdapter<T> : IDrawStrategy where T : IPaint, new() {
protected T _adaptee = new T();
public void draw() {
_adaptee.paint();
}
}
C#若要使用泛型的method,就得加上constraint,又因為使用delegation的方式,所以必須將泛型new起來,C#規定要在constraint加上new()。
C++/CLI by Generics
/**//*
(C) OOMusou 2007 http://oomusou.cnblogs.com
Filename : DP_AdpaterPattern_Strategy_ClassByGenerics.cs
Compiler : Visual Studio 2005 / C++/CLI
Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Generics
Release : 07/20/2007 1.0
*/
#include "stdafx.h"
using namespace System;
interface class IDrawStrategy {
void draw();
};
ref class Grapher {
public:
Grapher() {}
Grapher(IDrawStrategy^ drawStrategy) : _drawStrategy(drawStrategy) {}
public:
void drawShape();
void setShape(IDrawStrategy^ drawStrategy);
protected:
IDrawStrategy^ _drawStrategy;
};
void Grapher::drawShape() {
if (_drawStrategy != nullptr)
_drawStrategy->draw();
}
void Grapher::setShape(IDrawStrategy^ drawStrategy) {
_drawStrategy = drawStrategy;
}
interface class IPaint {
void paint();
};
ref class Triangle : public IPaint {
public:
virtual void paint();
};
void Triangle::paint() {
Console::WriteLine("Draw Triangle");
}
ref class Circle : public IPaint {
public:
virtual void paint();
};
void Circle::paint() {
Console::WriteLine("Draw Circle");
}
ref class Square : public IPaint {
public:
virtual void paint();
};
void Square::paint() {
Console::WriteLine("Draw Square");
}
generic<typename T>
where T : IPaint, gcnew()
ref class DrawAdapter : public IDrawStrategy {
public:
DrawAdapter() : _adaptee(gcnew T){}
public:
virtual void draw();
protected:
T _adaptee;
};
generic<typename T>
void DrawAdapter<T>::draw() {
_adaptee->paint();
}
int main() {
Grapher^ grapher = gcnew Grapher(gcnew DrawAdapter<Triangle^>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Circle^>);
grapher->drawShape();
grapher->setShape(gcnew DrawAdapter<Square^>);
grapher->drawShape();
}
執行結果
Draw Triangle
Draw Circle
Draw Square
72行
generic<typename T>
where T : IPaint, gcnew()
ref class DrawAdapter : public IDrawStrategy {
public:
DrawAdapter() : _adaptee(gcnew T){}
public:
virtual void draw();
protected:
T _adaptee;
};
C++/CLI的Generics寫法和C#類似,不過又不完全一樣。
VB by Generics
'
'(C) OOMusou 2007 http://oomusou.cnblogs.com
'
'Filename : DP_AdpaterPattern_Strategy_ClassByGenerics.vb
'Compiler : Visual Studio 2005 / VB 9
'Description : Demo how to use Strategy Pattern with Adpater Pattern (Class Adapter) By Generics
'Release : 07/20/2007 1.0
'
Imports System
Interface IDrawStrategyInterface IDrawStrategy
Sub draw()Sub draw()
End Interface
Class GrapherClass Grapher
Protected _drawStrategy As IDrawStrategy
Public Sub New()Sub New(Optional ByRef drawStrategy As IDrawStrategy = Nothing)
_drawStrategy = drawStrategy
End Sub
Public Sub drawShape()Sub drawShape()
If (_drawStrategy IsNot Nothing) Then
_drawStrategy.draw()
End If
End Sub
Public Sub setShape()Sub setShape(ByRef drawStrategy As IDrawStrategy)
_drawStrategy = drawStrategy
End Sub
End Class
Interface IPaintInterface IPaint
Sub paint()Sub paint()
End Interface
Class TriangleClass Triangle
Implements IPaint
Public Sub paint()Sub paint() Implements IPaint.paint
Console.WriteLine("Draw Triangle")
End Sub
End Class
Class CircleClass Circle
Implements IPaint
Public Sub paint()Sub paint() Implements IPaint.paint
Console.WriteLine("Draw Circle")
End Sub
End Class
Class SquareClass Square
Implements IPaint
Public Sub paint()Sub paint() Implements IPaint.paint
Console.WriteLine("Draw Square")
End Sub
End Class
Class DrawAdapterClass DrawAdapter(Of T As {IPaint, New})
Implements IDrawStrategy
Protected _adaptee As T = New T
Public Sub draw1()Sub draw1() Implements IDrawStrategy.draw
_adaptee.paint()
End Sub
End Class
Class ClientClass Client
Shared Sub Main()Sub Main()
Dim grapher As New Grapher(New DrawAdapter(Of Triangle))
grapher.drawShape()
grapher.setShape(New DrawAdapter(Of Circle))
grapher.drawShape()
grapher.setShape(New DrawAdapter(Of Square))
grapher.drawShape()
End Sub
End Class
執行結果
Draw Triangle
Draw Circle
Draw Square
62行
Class DrawAdapterClass DrawAdapter(Of T As {IPaint, New})
Implements IDrawStrategy
Protected _adaptee As T = New T
Public Sub draw1()Sub draw1() Implements IDrawStrategy.draw
_adaptee.paint()
End Sub
End Class
我第一次使用VB的泛型,不過我覺得VB泛型的語法挺好的,一目了然。
Conclusion
Design Pattern雖然是OO思維的產物,但很多時候可借用泛型讓Design Pattern更好用,如(原創) 我的Design Pattern之旅[3]:使用template改進Strategy Pattern (OO) (Design Pattern) (C/C++) (template) 及 (原創) 我的Design Pattern之旅[4]:使用Generic改進Strategy Pattern (OO) (Design Pattern) (.NET) (C#) 都曾經用泛型讓Strategy Pattern更加好用。CLI語言C#、VB、C++/CLI若要使用Generics,不能使用傳統Class Adapter的繼承方式,必須使用組合來delegation。C++/CLI因同時有Template和Generics,所以繼承和組合方法皆適用。