解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发
编译器中;在实际应用中,我们可能很少碰到去构造一个语言的文法的情况。
虽然你几乎用不到这个模式,但是看一看还是能受到一定的启发的。
定义:定义语言的文法,并且建立一个解释器来解释该语言中的句子,它属于类的行为模式。
1.抽象表达式角色:声明一个的解释操作,这个接口为所有具体表达式角色(抽象语法树中的节点)都要实现的。
抽象语法树的每一个节点都
代表一个语句,而在每个节点上都可以执行解释方法。这个解释方法的执行就代表这个
语句被解释。由于每一个语句都代表这个语句被解释。由于每一个语句都代表一个常见
的问题的实例,因此每一个节点上的解释操作都代表对一个问题实例的解答。
2。终结符表达式角色:具体表达式。
a)实现与文法中的终结相关联的解释操作
b)而且句子中的每个终结符需要方尺类的一个实例与之对应
3.非终结符表达式角色:具体表达式。
a) 文法中的每条规则 R::=R1R2…Rn 都需要一个非终结符表带式角色
b) 对于从 R1 到Rn 的每个符号都维护一个抽象表达式角色的实例变量
c) 实现解释操作,解释一般要递归地调用表示从 R1 到Rn 的那些对象的解释操作
4) 上下文(环境)角色:包含解释器之外的一些全局信息。
5) 客户角色:
a) 构建(或者被给定)表示该文法定义的语言中的一个特定的句子的抽象语法树
b) 调用解释操作
例子略/./以后用的上的时候在研究研究
四、优缺点
解释器模式提供了一个简单的方式来执行语法,而且容易修改或者扩展语法。一般系统
中很多类使用相似的语法,可以使用一个解释器来代替为每一个规则实现一个解释器。而且
在解释器中不同的规则是由不同的类来实现的,这样使得添加一个新的语法规则变得简单。
但是解释器模式对于复杂文法难以维护。可以想象一下,每一个规则要对应一个处理类,
而且这些类还要递归调用抽象表达式角色,多如乱麻的类交织在一起是多么恐怖的一件事
啊!
五、总结
这样对解释器模式应该有了些大体的认识了吧,由于这个模式使用的案例匮乏,所以本
文大部分观点直接来自于GOF 的原著。只是实例代码是亲自实现并调试通过的。
命令模式是从界面设计中提取出来的一种分离耦合,提高重用的方法。被认为是最优雅而且简单的模式,它的应用范围非常广泛。
定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
组成:1.命令角色:( Command):声明执行操作的接口。抽象类实现。
2.具体命令角色(Concrete Command):将一个接收者对象绑定于一个运作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。
3.客户角色(Client):创建一个具体命令对象(并可以设定它的接收者)。
4.请求者角色(Invoker):调用命令对象执行这个请求。
5.接收者角色(Receiver):知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
实例:
Class Command{
public:
~virtual Command(){}
virtual Execute() = 0;
};
class Receiver{
public:
void Action(){
std::cout << "Receiver Action\n";
}
};
class Invoker{
public:
Invoker(Command *pCommand){}
~Invoker(){
delete m_pCommand;
m_pCommand = NULL;
}
void Invoker(){
if (NULL != m_pCommand)
{
m_pCommand->Execute();
}
}
private:
Command m_pCommand;
};
class ConcreteCommand:public Command
{
public:
ConcreteCommand(Receiver *pReceiver)
: m_pReceiver(pReceiver)
{}
virtual ~ConcreateComand()
{
delete m_pReceiver;
m_pReceiver = NULL;
}
virtual void Execute()
{
if (NULL != m_pReceiver)
{
m_pReceiver->Action();
}
std::cout << "Execute by ConcreateComand\n";
}
private:
Receiver* m_pReceiver;
};
void main(){
Receiver* pReceiver = new Receiver();
Command* pCommand = new ConcreateComand(pReceiver);
Invoker* pInvoker = new Invoker(pCommand);
pInvoker->Invoke();
delete pInvoker;
}
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
下面是《设计模式》中给出的适用范围:
1) 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
2) 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3) 可处理一个请求的对象集合应被动态指定
责任链模式由两个角色组成:
1) 抽象处理者角色(Handler):它定义了一个处理请求的接口。当然对于链子的不同实现,
也可以在这个角色中实现后继链。
2) 具体处理者角色(Concrete Handler):实现抽象角色中定义的接口,并处理它所负责
的请求。如果不能处理则访问它的后继者。
//这是抽象处理者角色
public interface CodeAutoParse {
//这里就是统一的处理请求使用的接口
String[] generateCode(String moduleCode, int number, String rule,String[] target)
throws BaseException;
}
//这个为处理日期使用的具体处理者
public class DateAutoParse implements CodeAutoParse{
//获取当前时间
private final Calendar currentDate = Calendar.getInstance();
//这里用来注入下一个处理者,系统中采用Spring Bean 管理
private CodeAutoParse theNextParseOfDate;
public void setTheNextParseOfDate(CodeAutoParse theNextParseOfDate){
this.theNextParseOfDate = theNextParseOfDate ;
}
/*
*实现的处理请求的接口
*这个接口首先判断用户定义的格式是否有流水号,有则解析,没有则跳过
*下传到下一个处理者
*/
public String[] generateCode(String moduleCode, int number, String rule, String[]
target)
throws BaseException {
//这里省略了处理的业务
……
if(theNextParseOfDate != null)
return theNextParseOfDate.generateCode(moduleCode , number , rule,
target)
else
return target;
}
责任链模式优点,上面已经体现出来了。无非就是降低了耦合、提高了灵活性。但是责
任链模式可能会带来一些额外的性能损耗,因为它每次执行请求都要从链子开头开始遍历。
代理模式有俩个英文名字:Proxy Pattern SurrogatePattern
定义:为期货对象提供一种代理以控制对这个对象的访问。说白了就是,在一些情况下客户不想或
者不能直接引用一个对象,而代理对象可以在客户和目标对象之间起到中介作用,去掉客户
不能看到的内容和服务或者增添客户需要的额外服务。
JAVA与模式中将代理模式分为8种:常见的有1.远程代理(remote);2.虚拟代理(virtual);3.保护代理(Protect or Access);
4.智能引用(Smart Reference)代理等。
结构:
1.抽象主题角色:声明了真实主题和代理主题的共同接口。
2.代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相贩接口。
3.真实主题角色:定义真实的对象
class Subject
{
public:
Subject(){}
virtual ~Subject(){}
virtual void Request() = 0;
};
// 真正使用的实体
class RealSubject
: public Subject
{
public:
RealSubject();
virtual ~RealSubject(){}
virtual void Request()
{
std::cout << "Request By RealSubject\n";
}
};
// 代理类,含有一个指向RealSubject对象的指针
class Proxy
: public Subject
{
public:
Proxy()
: m_pRealSubject(NULL)
{
std::cout << "Constructing a Proxy\n";
}
virtual ~Proxy()
{
delete m_pRealSubject;
m_pRealSubject = NULL;
}
virtual void Request()
{
// 需要使用RealSubject的时候才去初始化
if (NULL == m_pRealSubject)
{
std::cout << "Request By Proxy\n";
m_pRealSubject = new RealSubject();
}
m_pRealSubject->Request();
}
private:
RealSubject* m_pRealSubject;
};
int main()
{
Subject* pProxy = new Proxy();
pProxy->Request();
delete pProxy;
return 0;
}
五、总结
代理模式能够协调调用者和被调用者,能够在一定程度上降低系统的耦合度。代理模式
中的真实主题角色可以结合组合模式来构造,这样一个代理主题角色就可以对一系列的真实
主题角色有效,提高代码利用率,减少不必要子类的产生。
定义:采用一个共享类来避免大量有相同内容的”小类“的开销。。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量
的细粒度对象,减少其带来的开销。
在名字和定义中都体现出了共享这一个核心概念,那么怎么来实现共享呢?事物之间都是不同的,但是又存在一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,享元模式中区分了内蕴状态和外蕴状态。内蕴状态就是共性,外蕴状态就是个性了。内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
享元模式分为:单纯享元模式和复合享元模式。
结构:
单纯享元模式的结构
1.抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
2.具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3.享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
4,客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
复合享元模式的结构
1.抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
2.具体享元角色:实现角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3.复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4。享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
5.客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
class Flyweight{
public:
virtual ~Flyweight(){}
string GetIntrinsicstring()
{
return m_State;
}
virtual void Operation(string & ExtricState) = 0;
protected:
Flyweight(const string & state)
:m_state(state)
{}
private:
string m_state;
};
class FlyweightFactory
{
public:
FlyweightFactory(){}
~FlyweightFactory(){
std::list<Flyweight*>::iterator iter1, iter2, temp;
for (iter1 = m_listFlyweight.begin(), iter2 = m_listFlyweight.end();
iter1 != iter2;
)
{
temp = iter1;
++iter1;
delete (*temp);
}
m_listFlyweight.clear();
}
Flyweight* GetFlyweight(const string& key)
{
std::list<Flyweight*>::iterator iter1, iter2;
for (iter1 = m_listFlyweight.begin(), iter2 = m_listFlyweight.end();
iter1 != iter2;
++iter1)
{
if ((*iter1)->GetIntrinsicState() == key)
{
std::cout << "The Flyweight:" << key << " already exits"<< std::endl;
return (*iter1);
}
}
std::cout << "Creating a new Flyweight:" << key << std::endl;
Flyweight* flyweight = new ConcreateFlyweight(key);
m_listFlyweight.push_back(flyweight);
}
private:
list<Flyweight*> m_listFlyweight;
};
class ConcreateFlyweight:public Flyweight
{
public:
ConcreateFlyweight(const string& state)
:Flyweight(state)
{}
virtual ~ConcreateFlyweight(){}
virtual voidOperation(string & ExtriunsiState)
{
}
}
void main()
{
FlyweightFactory flyweightfactory;
flyweightfactory.GetFlyweight("hello");
flyweightfactory.GetFlyweight("world");
flyweightfactory.GetFlyweight("hello");
}
//感觉例子举的不是很好 以后在改
五、使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来
了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分
妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是
一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不
使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。可以采用
相应的算法来提高查找的速度。
门面模式(facade)又称外观模式:为子系统中的一组接口提供一个一致的界面。
组成:
1.门面角色(facade):这是门面模式的核心。它被客户角色调用,因此它熟悉子系统的功能。它内部根据客户角色已有的需求预定了几种功能组合。
2.子系统角色:实现了子系统的功能。对它而言,facade角色就和客户角色一样是未知的,它没有任何facade角色的信息和连接。
3.客户角色:调用facede角色来完成要得到的功能。
Facade 模式的一个典型应用就是进行数据库连接。一般我们在每一次对数据库进行访
问,都要进行以下操作:先得到connect 实例,然后打开connect 获得连接,得到一个
statement,执行sql 语句进行查询,得到查询结果集。
四、使用环境和优点
《设计模式》给出了门面模式的使用环境:
1) 当你要为一个复杂子系统提供一个简单接口时。在上面已经描述了原因。
2) 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade 将这个子系统与
客户以及其他的子系统分离,可以提高子系统的独立性和可移植性(上面也提到了)。
3) 当你需要构建一个层次结构的子系统时,使用facade 模式定义子系统中每层的入口点。
如果子系统之间是相互依赖的,你可以让它们仅通过facade 进行通讯,从而简化了它
们之间的依赖关系。
以下是它的优点:
1) 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更
加方便。
2) 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。
松耦合关系使得子系统的组件变化不会影响到它的客户。Facade 模式有助于建立层次
结构系统,也有助于对对象之间的依赖关系分层。Facade 模式可以消除复杂的循环依
赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要。在大型软件系统中降
低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用
Facade 可以降低编译依赖性,限制重要系统中较小的变化所需的重编译工作。Facade
模式同样也有利于简化系统在不同平台之间的移植过程,因为编译一个子系统一般不需
要编译所有其他的子系统。
3) 如果应用需要,它并不限制它们使用子系统类。因此你可以让客户程序在系统易用性和
通用性之间加以选择。
定义:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
组成:
1.抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。
2.具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。
3.装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象定义的接口。
4.具体装饰角色(Concrete Decorator):负责给珈增加的功能。
//抽象基类,定义一个对象接口,可以为这个接口动态的添加职责。
class Component{
public:
Component(){}
virtual ~Comonent(){}
//纯虚函数,由派生类实现
vitual void Opation() = 0 ;
};
//抽象基类,维护一上指向Component对象的指针
class Decorator:publc Comonnt{
publc:
Decorator (Componnt* pComponent)
{
}
virtual ~Component(){
delete m_pComponent;
m_pComponent = NULL;
}
protected:
Compponnt *m_pComponent;
};
// 派生自Component,在这里表示需要给它动态添加职责的类
class ConcreateComponent
: public Component
{
public:
ConcreateComponent(){}
virtual ~ConcreateComponent(){}
virtual void Operation()
{
cout << "Operation of ConcreateComponent\n";
}
};
// 派生自Decorator,这里代表为ConcreateComponent动态添加职责的类
class ConcreateDecorator
: public Decorator
{
public:
ConcreateDecorator(Component* pComponent) : Decorator(pComponent){}
virtual ~ConcreateDecorator(){}
virtual void Operation()
{
m_pComponent->Operation();
AddedBehavior();
}
private:
void AddedBehavior()
{
cout << "AddedBehavior of ConcreateDecorator\n";
}
};
void main()
{
// 初始化一个Component对象
Component* pComponent = new ConcreateComponent();
// 采用这个Component对象去初始化一个Decorator对象,
// 这样就可以为这个Component对象动态添加职责
Decorator* pDecorator = new ConcreateDecorator(pComponent);
pDecorator->Operation();
delete pDecorator;
system("pause");
return 0;
}
透明和半透明
对于面向接口编程,应该尽量使客户程序不知道具体的类型,而应该对一个接口操作。
这样就要求装饰角色和具体装饰角色要满足Liskov 替换原则。像下面这样:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator(c);
JUnit 中就属于这种应用,这种方式被称为透明式。而在实际应用中,比如java.io 中往
往因为要对原有接口做太多的扩展而需要公开新的方法(这也是为了重用)。所以往往不能
对客户程序隐瞒具体的类型。这种方式称为“半透明式”。
//和我之前看的一个版本代码不是太一样,有点迷糊。说说我的想法和感受。从代码的结构来看,装饰者模式有一个主抽象基类Component,定义了一接口。有一个派生抽象在,维护了基类指针Decorator。派生类又有派生类
(ConcreateDecorator)用来实现Decorator;主抽象基类Component还有一个派生类用来基类接口,动态添加职责。
组合(Composite)模式:将对象以树形结构组织起来,以达成“部分----整体"的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
组成:
1抽象构件角色(Component):它为组合中的对象声明接口,也可以为共有接口实现缺省行为。
2树叶构件角色(Leaf):在组合中表示叶节点对象---没有子节点,实现抽象构件角色声明的接口。
3树枝构件角色(Composite):在组合中表示分支节点对象----有子节点,实现抽象构件角色声明的接口:存储子部件。
//组合中的抽象基类
class Component
{
public:
Component(){}
virtual ~Component(){}
//纯虚函数提供接口
virtual void Openration() = 0;
// 虚函数,提供接口,有默认的实现就是什么都不做
virtual void Add(Component * pChild)
{}
virtual void Remove(Component * pChild)
{}
virtual Component * GetChile(int nIndex)
{return NULL;}
};
// 派生自Component,是其中的叶子组件的基类
class Leaf:public Component{
public:
Leaf(){}
virtual ~Leaf(){}
virtual void Operation(){
std::cout << "Operation by leaf\n";
}
};
// 派生自Component,是其中的含有子件的组件的基类
class Composite:public Component{
private:
// 采用list容器去保存子组件
std::list<Component*> m_ListOfComponent;
public:
Composite(){}
virtual ~Composite()
{
//清list
}
virtual void Operation()
{
for (list<Component*>::iterator it = m_ListOfComponent.begin();
it != m_ListOfComponent.end();
++it)
{
(*it)->Operation();
}
}
virtual void Add(Component* pChild)
{
m_ListOfComponent.push_back(pChild);
}
virtual void Remove(Component* pChild)
{
std::list<Component*>::iterator iter;
iter = find(m_ListOfComponent.begin(), m_ListOfComponent.end(), pChild);
if (m_ListOfComponent.end() != iter)
{
m_ListOfComponent.erase(iter);
}
}
virtual Component* GetChild(int nIndex)
{
if (nIndex <= 0 || nIndex > m_ListOfComponent.size())
return NULL;
std::list<Component*>::iterator iter1, iter2;
int i;
for (i = 1, iter1 = m_ListOfComponent.begin(), iter2 = m_ListOfComponent.end();
iter1 != iter2;
++iter1, ++i)
{
if (i == nIndex)
break;
}
return *iter1;
}
};
void main()
{
Leaf *pLeaf1 = new Leaf();
Leaf *pLeaf2 = new Leaf();
Composite* pComposite = new Composite;
pComposite->Add(pLeaf1);
pComposite->Add(pLeaf2);
pComposite->Operation();
pComposite->GetChild(2)->Operation();
delete pComposite;
}
//组合模式 就像树一样 有多个叶子 叶子实现树干(抽象基类)的一个接口,树枝用来串树叶,把树叶弄到一起,即可以使用单个对象的接口方法,又可以使用组合对象的方法。有点懂了 嘿嘿
定义:将抽象部分与它的实现部分分享,使它们都可以独立地变化。
可以分析变化的各类,将不变的框架使用抽象类定义出来,然后再将变化的内容使用具体的子类来分别实现。这样面向客户的只是一个抽象类,这种方式可以较好的避免为抽象类中现有接口添加新的实现所速写的影响,缩小了变化带来的影响。
组成:
1.抽象角色(Abstraction):它定义了抽象类的接口而且维护着一个指向实现(Implementor)角色的引用。
2.精确抽象(RefinedAbstraction)角色:实现并扩充由抽象角色定义的接口。
3.实现(Implementor)角色:给出了实现类的接口,这里的接口与抽象角色中的接口可以不一致。
4.具体实现(ConcreteImplementor)角色:给出了实现角色定义接口的具体实现。
//抽象角色
class Abstraction{
public:
Abstraction(Implementor* pImplementor):: m_pImplementor(pImplementor)
{
}
virtual ~Abstraction();
void Operation()
{
pImplementor->OperationImpl();
}
protected:
Implementor* m_pImplementor;
};
// 为实现Abstraction定义的抽象基类,定义了实现的接口函数
class Implementor{
public:
Implementor(){}
virtual ~Implementor(){}
virtual void OperationImpl() = 0;
};
// 继承自Implementor,是Implementor的不同实现之一
class ConcreateImplementorA:public Implementor
{
public:
ConcreateImplementorA(){}
virtual ~ConcreateImplementorA(){}
virtual void OperationImpl();
};
// 继承自Implementor,是Implementor的不同实现之二
class ConcreateImplementorB:public Implementor
{
public:
ConcreateImplementorB(){}
virtual ~ConcreateImplementorB(){}
virtual void OperationImpl();
};
void main()
{
Implementor* pImplementor = new ConcreateImplementorA();
Abstraction* pAbstraction = new Abstraction(pImplementor);
pAbstraction.Operation();
delete *pImplementor;
delete *pAbstraction ;
}
//感觉桥梁模式主要也是一个抽象类,然后抽象类中的方法是由它维护着的另一个抽象类指针所调用的接口方法。具体调用哪个方法要看实例化哪个派生类的指针才行。
将一个类的接口转换成客户希望的另外一个接口。Adapter械使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
组成:
1目标(Target):定义Client使用的接口。
2被适配(Adaptee)角色:这个角色有一个已存在并使用了的接口,而这个接口是需要我们适配的。
3适配器(Adapter)角色:这个适配器模式的核心。它将被适配角色已有的接口转换为目标希望的接口。
在《设计模式》一书中将适配器模式分为类适配器模式和对象适配器模式。区别仅在于适
配器角色对于被适配角色的适配是通过继承完成的还是通过组合来完成的
// 需要被Adapt的类
class Target
{
public:
Target(){}
~Target(){}
virtual void Request() = 0;
};
// 与被Adapt对象提供不兼容接口的类
class Adaptee
{
public:
Adaptee(){}
~Adaptee(){}
void SpecialRequest();
};
// 进行Adapt的类,采用聚合原有接口类的方式
class Adapter
: public Target
{
private:
Adaptee* m_pAdptee;
public:
Adapter(Adaptee* pAdaptee)
: m_pAdptee(pAdaptee)
{
}
virtual ~Adapter()
{
delete m_pAdptee;
m_pAdptee = NULL;
}
virtual void Request()
{
std::cout << "Request of Adapter\n";
m_pAdptee->SpecialRequest();
}
};
void main()
{
Adaptee *pAdaptee = new Adaptee();
Target * pTarget = new Adapter();
pTarget->Request();
delete pAdaptee;
delete PTarget;
}
//感觉这个就是一个抽象基类 我去实现个派生类 来实现那个接口
//派生类里面有个另个类的成员变量 然后通过该接口的实现 去调用另一个类的方法
//为什么不直接在基类中实现呢???不是很懂 慢慢学习中