一.设计原则
1.六大设计原则
单一职责原则
里氏替换原则
依赖倒置原则
接口隔离原则
迪米特法则
开闭原则
开闭原则
当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
里氏代换原则
子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
依赖倒转原则
实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
接口隔离原则
为了约束接口、降低类对接口的依赖性。
迪米特法则(最少知道原则)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
单一职责原则
控制类的粒度大小、将对象解耦、提高其内聚性。
2.单一职责好处?
- 类的复杂性降低,实现什么职责都有清晰明确的定义
- 可读性提高,复杂性降低,那当然可读性提高了;
- 可维护性提高,可读性提高,那当然更容易维护了!
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
3.里氏替换?
只只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生主任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应.
- 子类必须完全实现父类的方法
- 子类可以有自己的个性
- 覆盖或实现父类的方法时输入参数可以被放大 (重载和重写)
- 覆写或实现父类的万方法时输出结果可以被缩小
4.里氏替换优劣?
优点
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性;
- 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
- 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
- 提高产品或项目的开放性。
缺点
继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
增强了耦合性。当父类的常量、变量和方法被修改时,必需要老
虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果–大片的的代码需要重构。
5.依赖倒置
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
抽象不应该依赖细节;
细节应该依赖抽象。
IDriver司机开车(不区分车类型),ICar车跑(区分车类型),2个接口
6.依赖倒置的优点
- 依赖倒置原则可以减少类间的耦合性,
- 提高系统的稳定性,
- 降低并行开发引起的风险,
- 提高代码的可读性和可维护性。
7.依赖倒置的传递
构造函数传递依赖对象
Setter方法传递依赖对象
接口声明依赖对象
8.接口隔离
建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。与单一职责原则不是相同的吗?错,接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口指什么?就是指提供给每个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。
- 接口要尽量小
- 接口要高内聚
- 定制服务
- 接口设计有限度的
9.迪米特法则
一个对象应该对其他对象有最少得了解,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用;如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
该原则其根本思想,是强调了类之间的松耦合;类之间的耦合越弱,越利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。在类的结构设计上,每一个类都应当尽量降低成员的访问权限。
10.开闭原则
软件实体应该可以扩展,但不可修改。该原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的可维护、可扩展、可复用、灵活性好。
设计人员必须对于他设计的模块应该对哪种变化封闭做出选择,必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。最初编写程序时假设变化不会发生,当变化发生时,就创建抽象来隔离以后发生的同类变化,拒绝不成熟的抽象。
二.具体模式
1.单例模式
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
主要优点
- 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。
主要缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
适用场景
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
2.单例模式的实现
public class Hand_01_Single {
private volatile static Hand_01_Single hand01Single;
public static Hand_01_Single getSingle() {
if (hand01Single == null) {
synchronized (Hand_01_Single.class) {
if (hand01Single == null) {
hand01Single = new Hand_01_Single();
}
}
}
return hand01Single;
}
}
3.工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式、在工厂模式中,我们在创建对对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
抽象程度:
简单工厂模式 < 工厂方法模式 < 抽象工厂模式。
角色分类:
1、简单工厂模式:抽象产品、具体产品、工厂类。
2、工厂方法模式:抽象产品、具体产品、抽象工厂、具体工厂。
3、抽象工厂模式:抽象产品族、抽象产品、具体产品、抽象工厂、具体工厂。
简单工厂模式
- 工厂类封装了创建具体产品对象的函数
- 扩展性非常差,新增产品的时候,需要去修改工厂类。
- 组成
- 工厂类(ShoesFactory):工厂模式的核心类,会定义一个用于创建指定的具体实例对象的接口。
- 抽象产品类(Shoes):是具体产品类的继承的父类或实现的接口。
- 具体产品类(NiKeShoes\AdidasShoes\LiNingShoes):工厂类所创建的对象就是此具有产品实例。
public interface Sender {
void send();
}
public class MailSender implements Sender{
public void send() {
System.out.println("send mail");
}
}
public class MessageSender implements Sender{
public void send() {
System.out.println("send message !");
}
}
public class SenderFactory{
public static Sender getSender(Class<? extends Sender> clazz){
if(clazz == null){return null;}
try{
return clazz.newInstance();
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}
public class SendTest {
public static void main(String[] args) {
SenderFactory.getSender(MailSender.class).send();
SenderFactory.getSender(MessageSender .class).send();
}
}
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
public interface Sender {
void send();
}
public class MailSender implements Sender{
public void send() {
System.out.println("send mail");
}
}
public class MessageSender implements Sender{
public void send() {
System.out.println("send message !");
}
}
public interface SenderFactory{
Sender getSender();
}
public class MessageSenderFactory implements SenderFactory{
public Sender getSender() {
return new MessageSender();
}
}
public class MailSenderFactory implements SenderFactory{
public Sender getSender() {
return new MailSender();
}
}
public class SendTest {
public static void main(String[] args) {
SenderFactory mailSenderFactory = new MailSenderFactory();
mailSenderFactory.getSender().send();
SenderFactory messageSenderFactory = new MessageSenderFactory ();
messageSenderFactory.getSender().send();
}
}
抽象工厂模式
提供一个创建一系列相关或者相互依赖的对象的接口,无须指定他们具体的类。
/**
* 支付接口
*/
public interface IPay{
void pay();
}
public class AliPay implements IPay{
@Override
public void pay(){
System.out.println("支付宝支付流程开始:");
}
}
public class WePay implements IPay{
@Override
public void pay(){
System.out.println("微信支付流程开始:");
}
}
public class UniPay implements IPay{
@Override
public void pay(){
System.out.println("银联支付流程开始:");
}
}
public class OverseasPay implements IPay{
@Override
public void pay(){
System.out.println("跨境支付流程开始:");
}
}
/**
* 退款接口
*/
public interface IRefund{
void pay();
}
public class AliRefund implements IRefund{
@Override
public void pay(){
System.out.println("支付宝退款流程开始:");
}
}
public class WeRefund implements IRefund{
@Override
public void pay(){
System.out.println("微信退款流程开始:");
}
}
public class UniRefund implements IRefund{
@Override
public void pay(){
System.out.println("银联退款流程开始:");
}
}
public class OverseasRefund implements IRefund{
@Override
public void pay(){
System.out.println("跨境支付退款流程开始:");
}
}
public abstract class IPaymentFactory {
abstract IPay getPayment();
abstract IRefund getRefund();
}
public class AliPaymentFactory extends IPaymentFactory {
@Override
public IPay getPayment() {
return new AliPay();
}
@Override
IRefund getRefund() {
return new AliRefund();
}
}
public class WePaymentFactory extends IPaymentFactory {
@Override
public IPay getPayment() {
return new WePay();
}
@Override
IRefund getRefund() {
return new WeRefund();
}
}
public class UniPaymentFactory extends IPaymentFactory {
@Override
public IPay getPayment() {
return new UniPay();
}
@Override
IRefund getRefund() {
return new UniRefund();
}
}
public class OverseasPaymentFactory extends IPaymentFactory {
@Override
public IPay getPayment() {
return new OverseasPay();
}
@Override
IRefund getRefund() {
return new OverseasRefund();
}
}
public class AbstractFactoryTest {
public static void main(String[] args) {
IPaymentFactory paymentFactory = new AliPaymentFactory();
paymentFactory.getPayment().pay();
paymentFactory.getRefund().refund();
}
}
4.模版方法模式
在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
代码复用
功能扩展
AQS-自定义锁
Mybatis-BaseExecutor
5.代理模式
意图:
为其他对象提供一种代理以控制对这个对象的访问。
适用性:
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一 些可以使用Proxy 模式常见情况:
远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。NEXTSTEP[Add94] 使用NXProxy 类实现了这一目的。Coplien[Cop92] 称这种代理为“大使” (Ambassador )。
虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的ImageProxy 就是这样一种代理的例子。
保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。例如,在Choices 操作系统[ CIRM93]中KemelProxies为操作系统对象提供 了访问保护。
智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:
对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers[Ede92 ] )。
当第一次引用一个持久对象时,将它装入内存。
在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
6.命令模式
命令是对命令的封装,每一个命令都是一个操作,请求方发出请求,接收方接收请求,并执行操作。命令模式解耦了请求方和接收方,命令模式属于行为型模式
- 接收者角色(Receiver):负责具体执行一个请求
- 命令角色(ICommand):定义需要执行的所有命令行为
- 具体的命令角色(ConcreteCommand):内部维护一个Receiver
- 请求者角色(Invoker):接收客户端的命令,并执行命令
优点
- 通过引入命令的抽象接口,实现了命令请求与实现的解耦
- 扩展性良好,可以很容易的增加新命令
缺点
- 命令类可能过多
意图:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
适用性:
抽象出待执行的动作以参数化某对象,你可用过程语言中的回调(call back)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
支持取消操作。Command的Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
7.责任链模式
意图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
适用性:
有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
可处理一个请求的对象集合应被动态指定。
8.装饰模式
意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。
适用性:
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
处理那些可以撤消的职责。
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
9.策略模式
意图:
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
适用性:
许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时[H087] ,可以使用策略模式。
算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
10.适配器模式
意图:
将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用性:
你想使用一个已经存在的类,而它的接口不符合你的需求。
你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
(仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
11.原型模式
意图:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用性:
当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者
为了避免创建一个与产品类层次平行的工厂类层次时;或者
当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
12.观察者模式
意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
适用性:
当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
13.门面模式
意图:
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性:
当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
14.享元模式
意图:
运用共享技术有效地支持大量细粒度的对象。
适用性:
一个应用程序使用了大量的对象。
完全由于使用大量的对象,造成很大的存储开销。
对象的大多数状态都可变为外部状态。
如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
应用程序不依赖于对象标识。由于Flyweight 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
三.分类
1.分类
根据模式是用来做什么的,可分为
- 创建型(Creational):创建型模式主要用于创建对象
- 结构型(Structural):处理类或对象的组合
- 行为型(Behavioral):描述对类或对象怎样交互和怎样分配职责
2.设计模式的优点
①可以提高程序员的思维能力、编程能力和设计能力。
②使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
③使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。现在这样说肯定有些懵逼,需要在实际开发中才能体会得到真正的好处。
3.创建型模式
- 简单(静态)工厂模式
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 原型模式
- 建造者模式
4.设计模式概述
创建型模式:
- 简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
- 抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
- 建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
- 单例模式:某个类只能有一个实例,提供一个全局的访问点。
- 原型模式:通过复制现有的实例来创建新的实例。
结构型模式
- 外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
- 桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
- 组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
- 装饰模式:动态的给对象添加新的功能。
- 代理模式:为其他对象提供一个代理以便控制这个对象的访问。
- 适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
- 亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
行为型模式
- 模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
- 策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
- 状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
- 观察者模式:对象间的一对多的依赖关系。
- 备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
- 中介者模式:用一个中介对象来封装一系列的对象交互。
- 命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
- 访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
- 责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
- 迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
5.各种关系的强弱顺序
泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖