本篇文章主要基于《深入设计模式》、《设计模式》、开源框架 protoActor-go 以及作者工作当中的一些浅显的感悟总结归纳而成。才疏学浅,期待共同进步(本文持续更新)。
想要深入学习的同学可以直接跳转链接:
https://refactoringguru.cn/design-patterns
创建型设计模式抽象了实例化过程,他们帮助一个系统独立于如何创建、组合和表示他的那些那些对象:
书本描述翻译:提供一个接口用于创建一系列相关或者相互依赖的对象,而无需指定他们具体的类(这是书本的原话,听晕了没有?)。翻译:一个创建对象的函数的输入是一个接口(由不通的类实现),不同的类实现的函数可以创建不通的具有相同属性(实现了相同的接口)的对象。
模块结构
(1)抽象产品(Abstract Product) 系列产品的 抽象接口
(2)具体产品(Concrete Product) 抽象接口的不同类型实现
(3)抽象工厂(Abstract Factory) 创建各种不同抽象产品的方法的接口
(4)具体工厂(Concrete Factory) 特指抽象工厂接口当中构建方法的具体实现,每个具体工厂仅能创建特定的 具体产品。
使用场景
(1)代码需要与多个不同系列的相关产品交互,但是由于 无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望现有的代码基于未来的具体类进行构建。在这种情形下,你可以采用抽象工厂模式。
抽象工厂为产品提供了一个接口,可以用于创建属于一个系列的产品对象。只要未来的 某个类 实现了该接口,那么他就能够生成应用程序所期待的可以用来使用的产品类型。
(2)如果你有一个基于一组抽象方法的类,且其主要功能因此变得不明确,那么在这种情况下可以考虑采用抽象工厂模式。
在设计良好的程序中,每一个类仅能负责一件事情。如果 一个类与多种类型产品 交互,就可以考虑将工厂方法抽取到独立的工厂类或者具备完整功能的抽象工厂类当中。
举例:
(1)protoActor-go 框架当中通过 props 输入参数生成不同的 Actor 就是一种典型的抽象工厂模式的应用。框架使用者们可以指定 props 这个工厂当中包含各种不同的 actor、mailbox、dispatcher、各种 Middleware 等等的 生成器(producer)。不同的生成器都实现了 抽象工厂 的接口**(xxxProducer)**,在调用的时候能够生成相应的实现了相同 抽象产品 接口的不同 Actor、mailbox等,从而实现拉起不同的 Process 并返回最终的 pid 信息。
(2)参考《深入设计模式》一书中的今典例子,阿迪、耐克两个不同的工厂分别生产各自的鞋子和袜子。
1: 意图:定义一个用于创建对象的接口,让子类决定实例化对象的类型。Factory Method 使一个类的实例化延迟到其子类。
2:动机:Factory Method 模式提供了一个解决方案。它封装了哪个 Document 子类将被创建的信息并将这些信息从框架当中分离出来。Application 的子类重定义 Application 的抽象操作 CreateDocument 以返回适当的 Document 子类对象。一旦一个 Application 子类对象实例化,他就可以实例化与应用相关的文档,而无需知道这些 文档的类 , 我们称 CreateDocument 是一个工厂方法,因为它负责 “生产” 一个对象。
3:模式结构
(1)产品(Product) 将会对接口进行声明,对于所有由创建者及其子类构建的对象,这些接口都是通用的。
(2)具体产品(Concrete Product) 是产品接口的不同实现
(3)创建者(Creator) 类声明返回产品对象的工厂方法,该对象的返回类型必须与产品接口相匹配。我们也可以将工厂方法声明为 抽象方法,强制要求每个子类以不同的方式实现该方法。
(4)具体创建者(Concrete Creator) 将会重写基础工厂方法,使其返回不同类型的产品。
注意: 工厂方法不一定是死板地只会创建实例,他还可以返回缓存、缓存池或者其他来源的已有对象。
4:适用场景
(1)在编写代码的过程当中,如果我们无法预知对象确切类别及其依赖关系的时候,我们就可以使用工厂方法。
(2)如果你希望用户能扩展你的软件库或者框架的内部组件。这个时候可以选择工厂方法。
(3)如果你希望复用现有对象来节省系统资源,而不是每次重新创建对象,可以使用工厂方法。
5:举例
(1)在 protoactor-go 框架中,比较核心的一个部件就是 mailbox,该模块是实现各个 Actor 之间通信的基础。框架本身提供了原始的 defaultMailbox 以供使用,但是框架只是提供了一个通用的符合大多数业务场景能够通用的邮箱机制。但是往往我们在某些特定的业务场景下需要实现自己特有的 mailbox 这个时候我们可以定义一个本项目组特有的 mailbox模块 该模块只需要实现 产品接口,然后就可以通过实现框架提供的 创建者接口。从而将自己设计创建的邮箱嵌入到框架当中一共本项目组使用。
结构型涉及如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。
行为型模式涉及算法和对象间职责的分配。行为型模式不仅描述对象或类的模式,还能描述他们之间的通信模式。
type Props struct {
spawner SpawnFunc
producer Producer
mailboxProducer mailbox.Producer
guardianStrategy SupervisorStrategy
supervisionStrategy SupervisorStrategy
dispatcher mailbox.Dispatcher
receiverMiddleware []ReceiverMiddleware
senderMiddleware []SenderMiddleware
spawnMiddleware []SpawnMiddleware
receiverMiddlewareChain ReceiverFunc
senderMiddlewareChain SenderFunc
spawnMiddlewareChain SpawnFunc
contextDecorator []ContextDecorator
contextDecoratorChain ContextDecoratorFunc
}
对应的各类 chain 也是可以框架使用者自己设计指定的,通过指定各个切片类型的 MiddleWare 从而实现组装消息处理 链条。
1:书本翻译描述:给定一个语言,定义他文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。翻译:对于某一类特定的问题,定义一套固定是解决方法。当遇到该特定类型的问题时,直接调用对应的 expression 解决该问题。
2:举例:暂无?
1:书本描述翻译:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。翻译:将各个对象之间的可能存在的复杂的相互调用逻辑封装到一个指定的 mediator类 当中,从而限制各个对象之间的直接交互,迫使他们都通过一个中介对象进行合作,从而减少对象之间混乱无序的依赖关系。
2:作用:中介者模式建议停止组件之间的直接交流从而使得组件之间彻底解耦,这些组件必须通过特殊的中介对象组合重定向调用行为,从而实现以间接的方式进行合作。最终的效果就是,组件仅仅依赖于一个中介者类,而无需与其他多个组件相耦合。
对于各个组件来说,中介者看上去完全就是一堵墙,发送者不知道墙的对面会是谁来处理自己的请求,接受者也不知道墙的对面是谁给我发了请求。
3:适用场景
(1)当一些对象和其他对象紧密耦合以至于难以对其进行修改的时候,可以使用中介者模式将他们之间的 依赖关系 抽象出来成为一个单独的类。
(2)当一个组件过于依赖于其他组件而无法再不同的系统中复用的时候,可以采用中介者模式,这样不同的应用就可任通过各自的 中介者类 对其进行复用
(3)如果为了在不同的情形下面复用一些基本的行为,导致你被迫需要创建大量的组件子类的时候,可以使用中介者模式,通过中介者实现各个组件之间的不同的组合方式。
4:举例:、
(1)典型的MVC架构模型
(2)参考《深入设计模式》一书中的火车站的例子,两列火车先后到达同一个车站,两列 火车对象 之间不会产生任何通信。那么 车站类 就充当了这两列火车对象之间的中介者,当前一列火车离开的时候会向车站发出通知,车站确认前一列火车离开之后方可允许下一列火车进站。
1:书本描述翻译:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
2:模式结构
(1)原发器(Originator) 原发器类(客户端类)可以生成自身状态的快照,也可以在需要的时候通过快照恢复自身状态。
(2)备忘录(Memento) 是原发器状态快照的值对象(value object),通常做法是将备忘录设置为不可变的,通过构造函数一次性传递数据
(3)负责人(Caretaker) 仅知道 “何时” 以及 “为何” 捕捉原发器的状态、以及何时恢复状态。负责人通过保存 备忘录栈,来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人将从栈顶获取备忘录,并将其传递给原发器的 恢复(restoration) 方法。
3:适用场景
(1)当你需要创建对象状态快照来恢复其之前的状态时,可以使用备忘录模式。备忘录模式允许你复制对象中的全部状态(包括私有成员变量),并将其独立于对象进行保存。该模式在处理事务回滚的时候必不可少。
(2)当需要限制其他对象直接访问本对象的成员变量的时候,可以使用该模式。备忘录让对象自学负责创建其对象状态的快照,任何其他对象都不能够读取快照,这有效的保障了数据的安全性。
4:举例
最常见的我们日常使用的文本编辑器,几乎所有的文本编辑器都具备操作撤销的功能。通过该功能用于可以按照先后顺序撤销先前执行的每一步操作。
1:书本描述翻译:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都将得到通知并被自动更新。观察者模式是一种行为设计模式,允许你定义一种订阅机制,可以在对象事件发生的时候通知多个 观察 该对象的其他对象。
2:模式结构
(1)事件(event) 事件就是对象之间进行交流的 信息流,在该信息流当中包含着需要传递的各种有用的信息。该信息称作 事件。
(2)发布者(Publisher) 会向其他对象发送值得关注的 事件(event),事件会在发布者自身状态改变或者执行特定行为后发生。发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅框架。当事件发生的时候,发送者会遍历订阅列表并调用每个订阅者对象的通知方法。该方法是在订阅者接口中声明的。
(3)订阅者(Subscriber) 可以执行一些操作来回应发布者的通知。所有的具体 订阅者类 都实现了同样的接口,因此发布者不需要与具体的类相耦合(这里有点策略模式的味道)。订阅者通常需要一些上下文信息来正确处理更新。因此,发布者通常会将一些上下文数据作为通知方法的参数进行传递。发布者也可以将自身作为参数进行传递,使订阅者直接获取所需要的数据。
(4)客户端(Client) 会分别创建发布者和订阅者对象,然后为订阅者注册发布者信息。
3:适用场景
(1)当一个对象的状态变化需要改变其他对象,或实际对象是事先未知的或者动态变化的时,可以使用观察者模式。
(2)当应用中的一些对象必须观察其他对象时候,可以使用该模式。但仅能在有限的时间内或者特定的情况下使用。订阅列表的动态的,因此订阅者可以随时加入或者离开列表。
4:举例
(1)在 protoActor-go 框架当中,actorContext 实现的 basePart 接口当中有一个 watch 方法,一个 Actor 通过该方法 watch(pid) 去 monitor 目标 Actor,目标 Actor 在收到 watch 消息之后会将该 pid 保存至 actorContextExtras 的 watchers(PIDSet)当中,当目标 actor 的状态发生改变的时候会广播通知 watchers 当中的每一个 pid 对象。
1:书本描述翻译:允许一个对象在其内部状态发生改变时改变他的行为。对象看起来似乎修改了他的类。翻译:一个对象对外部请求的响应随着自生状态的变化而变化。
有限状态机 程序在任意时刻仅处于几种有限的状态中,在任何特定的状态下程序的行为都不相同,可以瞬间从一个状态切换到另外一个状态。不过,根据当前状态,程序可能会切换到另外一种状态,也可能会保持当前状态不变,这些数量有限且预先定义的状态切换规则则被称作转移。
2:模式结构
(1)上下文(Context) 保存了对于一个具体的 状态对象 的引用,并会将所有与该状态相关的工作委派给它。上下文通过接口与状态对象交互,并且会提供一个设置器用于传递新的状态对象。
(2)状态(State)接口 会声明特定于状态的方法,这些方法能够被其他所有具体状态所理解,因为你不希望某些状态所实现的方法永远不被调用。
(3)**具体状态(Concrete State)**会自行实现状态接口的方法。同时,为了避免多个状态对象当中包含相似的代码,我们可以提供一个封装有部分通用行为的中间抽象类。
状态对象可以存储上下文对象的反向引用,然后可以通过该引用从上下文获取所需要的信息,并且能触发状态转移。
3:使用场景
(1)如果对象需要根据自身当前状态进行不同的行为,同时装态的数量非常多且与状态相关的代码会频繁变更的话,就需要考虑使用状态模式。状态模式建议将所有 特定于状态的代码 抽取到一组独立的类中,这样就可以实现各种不同状态之间的充分解耦,独自实现各自状态自己的功能。
(2)如果某个类需要根据成员变量当前的状态值改变自身的行为,从而需要使用大量的条件语句的时候,就需要考虑使用状态模式。
(3)当相似状态和基于条件的状态机转换中存在许多重复代码的时候,可以使用状态模式将公用的代抽取到一个新的基类当中从而减少代码的复用,提高解耦性。
4:举例
(1)在 protoActor-go 框架当中,每一个 process 都会继承一个 RouterState 对象
type process struct {
parent *actor.PID
router *actor.PID
state State
mu sync.Mutex
watchers actor.PIDSet
stopping int32
actorSystem *actor.ActorSystem
}
很显然实现 RouterState 接口的类不止一种,因此,process 在不同的时刻调用的 RouterState 接口的具体的实现肯定也是不同的。
(2)假设我们需要需要喝水,天气就是我们依赖的状态。在不同的天气下我们都要喝水,但是不同的天气我们喝水的过程可能就是不一样的,天气热的时候我们需要喝冷水,天气冷的时候我们需要喝热水。
1:定义:策略是一种行为模式,它将一组行为封装到一个类或者对象当中,并在上下文对象内部添加对这些行为对象的引用,并且实现这些对象之间的相互替换。
2:模式结构
(1)上下文(Context) 维护指向具体策略的引用,且仅通过策略接口与该对象进行交流
(2)策略(Strategy)接口 是所有具体策略的通用接口,它声明了一个上下文用于执行策略的方法。
(3)具体策略(Concrete Strategies) 实现了上下文所用的各种不同算法的不同变体,所有策略类都实现了相同的策略接口。
(4)客户端(Client) 会创建一个特定策略对象并将其传递给上下文,上下文会提供一个设置器以便客户端在运行的时候随时替换相关的策略
(5)当下文需要运行算法的时候,它会在自己已连接的策略对象上调用执行方法。上下文不清楚其所涉及的策略类型与算法的执行方式。
3:举例
(1)在 protoActor-go 框架当中每一个 actor 的 props 参数当中都会组合一个 supervisionStrategy 接口用于处理子 actor 的 failing 信息。
type Props struct {
spawner SpawnFunc
producer Producer
mailboxProducer mailbox.Producer
guardianStrategy SupervisorStrategy
supervisionStrategy SupervisorStrategy
dispatcher mailbox.Dispatcher
receiverMiddleware []ReceiverMiddleware
senderMiddleware []SenderMiddleware
spawnMiddleware []SpawnMiddleware
receiverMiddlewareChain ReceiverFunc
senderMiddlewareChain SenderFunc
spawnMiddlewareChain SpawnFunc
contextDecorator []ContextDecorator
contextDecoratorChain ContextDecoratorFunc
}
不同的actor的Props维护对supervisionStrategy对象的一个引用。一旦接收到子actor发送过来的failing信息,它就将这个消息转发给实现SupervisorStrategy的那个对象,例如allForOneStrategy、oneForOne不同的实例对象各自实现不同的HandleFailure功能。
(2)思考一下 Linux 操作系统常见的构建缓存的情形,由于处于内存当中,故其大小会存在限制,在达到限制上限以后,一些条目就必须被移除以留出空间。此类操作可以通过多种算法进行实现,一些流行的算法包括:
最少最近使用(LRU) 移除最近最少使用的一条条目
先进先出(FIFO) 移除最早创建的条目
最少使用(LFU) 移除使用率最低的一条条目
问题在于我们如何将 缓存类 与这些算法解耦,以便在运行时更改算法。此外,在添加新的算法时,缓存类不会因此改变。这就是 策略模式 发挥作用的典型场景,可以通过创建一系列不同的类来实现一系列不同的算法,但是这些算法全部实现的是相同的接口,这样就可以使得算法之间可以相互替换。这样我们通过给 缓存(caceh)类 赋予不同的 策略类 就可以不同缓存算法的调用。而不需要对这些类进行任何的修改。
1:书本描述翻译:定义一个操作中的算法骨架,而将一些步骤延迟到子类当中。TemplateMethod使得子类可以不改变一个算法的结构即可以重定义该算法的某些特定的步骤。
2:模式结构
(1)抽象类(AbstractClass) 声明作为算法步骤的方法,以及依次调用他们的模板方法,算法步骤可以被声明为抽象类型(接口),也可以提供一些默认的实现。
(2)具体类(ConcreteClass) 可以重写所有步骤(接口方法),但是不能重写模板方法自身。
3:举例
(1)在我们代码框架当中的一个典型应用便是NewBaseTemplateProps方法,这是一种典型的模板方法模式。NewBaseTemplateProps()方法规定了一个固定的用于生成 actor.Props 的算法步骤。但是并没有具体地去实现该算法步骤当中的各个函数的详细实现——具体的函数实现以及参数内容完全由该模板方法的调用者实现。
(2)让我们来考虑一个一次性密码功能(OTP)的例子,将 OTP 传递给用户的方式多种多样(短信、邮件等),但是无论采用哪种方式,整个 OTP 的流程都是相同的:生成随机数字、在缓存中保存这组数据以便进行后续验证、准备内容、发送通知、发布;后续引入的任何新 OTP 类型都很有可能需要进行相同的上述步骤。
因此,我们于是就有了这样一种场景,其中某个特定操作的步骤都是相同的,但是实现方式却可能有所不同,这正是适合考虑使用模板模式的情况。
首先,我们定义一个由固定数量的方法组成的基础模板算法,也就是我们的模板方法。然后我们的具体的不同的 处理类 会实现每一个步骤的方法,但是不会改变模板方法。
1:书本描述翻译:访问者模式允许你在结构体中添加行为,而又不会对结构体造成实际的变更。
2:模式结构
(1)访问者(visitor)接口 声明了一系列以对象结构体的具体元素为参数的访问者方法。
(2)具体访问者(Concrete Visitor) 不同的 访问者类 会实现相同的访问者接口。
(3)元素(Element) 接口声明了一个方法(accept(v visitor))来 “接收” 访问者,该方法的入参必须为 访问者接口。
(4)客户端(Client) 通常作为集合或者其他复杂对象的代表。客户端通常不知晓所有的具体元素类,因为他们会通过抽象接口与集合中的对象进行交互。
3:适用场景
(1)访问者模式通过在访问者对象中为多个 目标类 提供具有相同操作的变体,让你能在属于不同类的一组对象上面执行同一操作。如果你需要对一个复杂的对象结构中的所有元素执行某些操作,那么可以考虑使用访问者模式。
(2)可以使用访问者模式来清理属于辅助行为的业务逻辑。通过访问者模式将所有的非主要行为抽取到一组访问者类中,使得程序的 主要类 能够更专注于主要的工作。
(3)当某些行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义的时,可以使用该模式将那些特有的行为抽取到单独的访问者类中,只需实现接收相关类的对象作为参数的访问者方法,并将其他方法留空即可。
4:举例
(1)参考《深入设计模式》一书中内容。假设你是一个代码库的维护者,代码库中包含不同形状的结构体,如:方形、圆形、三角形。上述每个形状结构体都实现了通用形状接口。在业务演进的过程中,你的代码库会被各种各样的新的功能所淹没。例如,有个团队想要在你的形状结构体当中添加一个 getArea() 获取面积的请求。
一般情况下我们第一反应想到的肯定是直接将 getArea() 方法添加至形状接口,然后在各个形状类中实现它。这么做没有错,但是考虑到代码的演进,如果在来一百个这样的新的请求呢?我们是不是要在形状接口当中新增100个方法?这种做法显然是不行的。如果这么搞,团队 B 的业务需求岂不是全部落到团队 B 身上了?搞不好再出问题,做好了功劳是别人的,做不好锅自己背。
这个时候 访问者模式 就派上用场了,定义一个 访问者接口(visitor),该接口里面包含了请求者的一系列想要新增的方法,例如:
type visitor interface {
visitForSquare(square)
visitForCircle(circle)
visitForTriangle(triangle)
}
接口当中各个方法的输入就是本项目组的 不同形状的结构体,这样一来请求方想要实现什么样的行为就有请求方在他们自己的 访问者接口 里面自己去执行就好了。出了问题也是他们自己的,我们不背锅~~。
在程序设计领域,SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖倒置)是由 罗布特.马丁 在21世纪早期引入的,指代了面向对象编程和面向对象设计的五个基本原则。当这些原则一起使用的时候,它们使得程序员开发一个易于维护和扩展的系统变得可能。
1、S 单一功能原则
面向对象编程领域中,**单一功能原则(Single responsibility principle)**规定每个类或者对象都应该仅具有单一的功能职责,并且这些功能因该由这个类完全封装起来。所有由这个类提供的服务都应该严格的和该功能平行(功能平行意味着没有额外的功能依赖)。
马丁把功能(职责)定义为 改变的原因,并且认为 一个类或者一个模块应该有且仅有一个改变的原因。举一个简单的例子:假设我们一个人吃午饭的动作有两个 吃饭 + 喝水,按照单一功能的原则,吃饭和喝水者这两个完全分离的功能需要通过两个不同的类(模块)来实现。如果将这两个功能耦合在一起,在吃饭的同时去喝水显然是很容易 出问题的。
2、O 开闭原则
在面向对象编程领域中,开闭原则规定软件中的对象(类、模块等)应该只允许扩展而不允许修改。这意味着对于一个模块而言,你只能在不会改变它源代码的情况下改变他的行为。该约束在产品化的环境当中是非常有价值的。在实际的商业化产品中,改变已有的代码是一件非常麻烦且危险的事情,需要经过代码审查、单元测试、环境验证等一系列的操作,以确保已有产品的功能不会应为新增代码的修改而改变从而导致不可预知的网络事故。
3、L 里氏替换原则
这个原则很简单,一句话描述就是 子类对象能够在使用基类对象的地方替换基类。
4、I 接口隔离原则
接口隔离原则指的是,使用者应该不依赖于它不使用的方法。通过拆分非常庞大的且臃肿的接口为一个个更小的按照具体功能模块划分的接口。这样依赖使用者就感知不到那些他们不需要使用或者不能使用的方法。这种细分得到的接口通常被称作 角色接口(role interface),接口隔离的目的是进一步将系统的各个功能模块解耦,从而使其更加容易重构,以及更加方便重新部署。
5、D 依赖反转原则
在面向对象设计的领域中,依赖反转原则指的是一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略则应用在低层次上)形式,高层次的模块不应该依赖于低层次模块的实现细节,依赖关系被颠倒,最终使得低层次的模块依赖于高层次的模块。
该原则规定:高层次的模块不应该依赖于低层次的模块,两者都应该依赖与抽象接口,抽象接口不应该依赖于具体实现,而具体实现应该依赖于抽象接口。
在传统的应用架构当中,低层次的组件被设计用于被高层次组件使用,这一点提供了逐步构建一个复杂系统的基本思路。在这种结构下,高层次组件直接依赖于低层次的组件去实现所需要的任务,这种对于低层次的依赖限制的高层次组件被复用的可行性。
依赖反转原则的目的 是把高层次组件从对低层次组件的依赖中解耦出来,这样高层次组件就可以组合使用不同层次的组件。通过把高层次组件和低层次组件划分到不同的 包/库 当中的方式促进这种解耦。由于底层组件是对高层组件接口的具体实现,因此低层组件包的编译是依赖于高层组件的。
依赖反转原则同样被认为是应用了适配器模式:高层模块定义了它自己的适配器接口(高层依赖的抽象接口),被适配的对象同样依赖于适配器接口对象(因为它实现了这个接口)。通过这种方式,高层组件则不用依赖于低层组件,因为它仅间接地通过调用适配器接口的多态方法去使用低层组件,而这些方法则是由被适配的模块实现的。
1、封装
封装就是将客观的事务抽象为逻辑的实体,实体的属性和功能相结合,形成一个有机的整体。同时对实体的属性和功能实现进行访问控制,向信任的实体开发,向不信任的实体隐藏。通过开放的外部接口可以访问,而无需知道功能如何实现。
2、继承
在继承的机制下可以形成有层级的类,使得低层级的类可以访问高层及的类的方法和属性。继承的方法有很多:实现继承、接口继承等。
3、多态
多态指的是一个类的同名方法,在不同情况下的实现细节不同。多态机制通过 不同的模块 使得共同的外部接口有着不同的内部实现。总的来说,多态有以下目的:一个外部接口可以被多个类使用,不同对象调用同个方法,可有不同的实现。