概述
本文大部分内容来自《大话设计模式》。
java很适合多人协作,但代码量很大。
与其抱怨需求总是变化,不如改变开发过程,从而更有效地应对变化。面向对象就是为了解决变化的问题。
文章目录
- 1.封装
- 2.框架+数据
- 3.抽象,抽象类,接口
- 4.MVC设计模式
- 5.继承
- 6.多态
- 7.从活字印刷看面向对象的好处
- 8.耦合
- 9.业务与界面分离
- 10.UML类图
- 11.简单工厂模式
- 12.策略模式
- 13.单一职责原则
- 14.开放-封闭原则
- 15.里氏代换原则
- 16.依赖倒转原则
- 17.装饰模式
- 18.代理模式
- 19.工厂方法模式
- 20. 原型模式
- 21.模板方法模式
- 22.迪米特法则
- 23.外观模式
- 24.建造者模式
- 25.观察者模式
- 26.抽象工厂模式
- 27.状态模式
- 28.备忘录模式
- 29.组合模式
- 30.迭代器模式
- 31.单例模式
- 32.桥接模式
- 33.命令模式
- 34.职责链模式
- 35.中介者模式
- 36.享元模式
- 37.解释器模式
- 38.访问者模式
- 39.设计模式总结
1.封装
封装能降低耦合,易于维护。
封装的具体手段如,尽可能让成员变量为私有变量。
一种糟糕的封装是,直接把private变量通过接口发出去。
2.框架+数据
经典的命令行程序写成oop之后,可以把不同的命令注册为不同的handler。之后命令的增删就变为hashmap(String,handler)的增删。
如果类内有多个同类型的成员变量(比如城堡游戏中,每个房间有东南西北四个邻居,东南西北就属于同类型的成员变量),考虑使用容器,比如hashmap。
这两处的hashmap,称之为框架+数据。hashmap构建的映射是框架,东南西北、不同命令字符串对应的handler是数据。
3.抽象,抽象类,接口
以animal为例。
抽象类不能被实例化。(实例化一个动物是不恰当的,动物长什么样?)
抽象方法是必须被子类重写的方法。
如果类中包含抽象方法,那么类就必须定义为抽象类。
抽象类应该拥有尽可能多的抽象代码,尽可能少的数据。
抽象类设计出来就是用来继承的,在继承树状关系中,树枝节点应为抽象类,树叶节点应为具体类。
抽象类是自底向上抽象出来的,接口是自顶向下设计出来的。
4.MVC设计模式
model是数据,view是表现,control是控制。
model中的数据修改后,通知view进行修改。
control负责接收用户的动作,修改model中的数据。
control不直接与view打交道,即用户在界面上所做的操作,不直接决定显示内容。
JTable是view和control的合并。
数据与表现分离 比如常见的跟view有关的类,只负责取数据然后显示。比如JTable,只以表格形式显示数据,而不负责存储数据。
5.继承
比如cd类和dvd类很相似,此时为了代码复用,可以提取出一样的部分作为父类。
父类中的private,子类无法继承。改为protected子类即可继承。
子类构造函数中如果不写super,会调用父类的无参构造函数。写了则按写了来(super必须在第一句,先父后子永远成立)。
子类可以声明和父类中同名的成员,这时会有两份同名成员。在子类的函数中操作,会因为就近原则选择子类中声明的。在父类的函数操作中,则选择继承下来的。
子类可以override父类中同名的函数,如果想调用父类中同名的函数,可以使用super关键字。
6.多态
还是以animal为例。
java多态的实现,与其动态绑定机制有关。
java中默认是动态绑定(调用函数时选择实际执行的函数时,依据是动态类型)。比如父类.方法,可能调用的是父类的方法,也可能调用的是子类的方法,取决于动态类型。
7.从活字印刷看面向对象的好处
易维护性:当曹操的需求变化时,只需要修改几个字,而不用重新刻板
可扩展性:可以加字,而不用重新刻板
灵活性:句子的顺序可以变,而不用重新刻板
可复用:每一个活字都能反复用,而雕版只能用一次
8.耦合
低耦合:每个活字块之间是独立的。
高耦合:整块雕版上的字是共进退的。
9.业务与界面分离
比如业务是四则运算,界面是PC端的用户输入。
如果二者分离,那么同样的业务还可以配合其它的界面进行使用。
这就是业务封装的好处。
10.UML类图
unified modeling language 统一建模语言
聚合是弱相关,组合是强相关。
关联是指src了解dst。
11.简单工厂模式
工厂负责将类实例化,就好像在照着类的图纸生产对象一样。
工厂要用到的图纸可能经常会变化,比如新的handler类,或者新的运算类。因此,将工厂单独设为一个类,方便修改。
例子是计算器。使用简单工厂模式就能快速添加对数、三角函数等新的运算。
简单工厂模式中,工厂类的create方法的入参往往直接来自界面的字符串。这样可以保证业务与表现分离(即表现端不需要了解有什么图纸,一股脑给工厂即可)。
12.策略模式
书中举了超市收银的例子,超市有很多促销策略,比如满减、打折、积分等。如果有多种算法,且它们经常互相替代,则适合使用策略模式。
上面是简单工厂模式下的超市收银,下面是策略模式下的超市收银。类不是越多越好,还是要遵循抽象的原则。比如所有的满减类,我们抽象概括为return,而不是每一种情况都写一个类。
normal、rebate和return都继承自super,它们三个是可以互相替换的策略,都实现了“计算应收货款”这一方法,它们内部的差别可以很大,入参数量和类型都可以不同。
context中有一个cashsuper成员,结合简单工厂方法后,令context接收一个用户字符串(这样用户不需要知道内部对策略的命名,如CashRebate,用户只需要输入字符串“打折”即可。),在context内初始化对应的具体策略,赋给cashsuper成员变量。在下图中可以看见,用户只需要与context类交互即可。
在策略模式中,定义一系列的算法,通过相同的方法调用(context),这些算法的入参数量可能不同,比如打折算法和满减算法的入参数量就不同。而context则屏蔽了算法入参数量上的差异,且直接提供了getResult接口,使用更加方便。使用算法类和算法类之间的耦合进一步降低了(简单工厂模式下客户端需要认识cashsuper和cashfactory两个类,而策略模式下只需要认识cashcontext一个类)。
13.单一职责原则
只有一个原因能引起类的变化。
比如之前提到的业务和界面分离,就是单一职责原则的体现。
long method是一种bad smell,这意味着其责任过大。面向对象设计应尽量分解责任。
14.开放-封闭原则
对于扩展是开放的,对于修改是封闭的。
比如一国两制,扩展了,但是没有修改社会主义和资本主义。
对于频繁变化的部分,将之抽象为类,比如简单工厂模式中的工厂。
15.里氏代换原则
liskov substitution principle
子类可以替换父类。
企鹅不能继承鸟类,因为企鹅不会飞,不符合LSP。
只有这样,父类才能被复用。复用其实就是变化中的不变,即不管更换为哪一个子类(比如,刚写的针对新需求的子类),仍然能正常工作。
16.依赖倒转原则
在面向过程编程中,高层依赖低层。比如高层常常会用到访问数据库的低层函数。
如果不想改高层,但却想更换低层的存储方法,会发现高层不得不改。
符合依赖倒转原则的、合理的做法应该是:
而不是:
以电脑部件为例。主板和各组件如果都面向接口编程,那么更换高层和低层都没问题。
如果各组件依赖某一个品牌的主板,那么更换主板之后,各组件可能无法正常工作。
17.装饰模式
装饰模式的主要作用就是给对象灵活动态添加职责。
装饰模式,就好像给人穿衣服。下图中,人是component,服饰类是decorator(更符合情景的说法是,穿了衣服的人是decorator,可以给一个decorator继续加衣服),具体的服饰是concrete decorator。
至于为什么decorator需要继承component,我想是为了方便装饰,所谓装饰,就是在原来的基础上做小修改,为了方便获取“原来的基础”,才继承component。看下面的代码就能明白:
在原有operation的基础上增加operation,这才叫装饰。
此外还有一个更重要的原因,就是我们希望能够在component上加一个或多个装饰。第一个装饰肯定是放在一个没穿衣服的人身上,所以输入应该是component类;而如果想在一个已经穿了一件衣服的人身上继续加衣服,可以将上一步的输出作为输入。说白了就是输入和输出最好继承自同一个父类,这样方便多次装饰。
其实此处用人和衣服的比喻并不完美,主要就是不好解释为什么衣服要继承自人。但是代码中确实有精妙之处,
注意此处输出的顺序,先是最后一层的大T恤,然后调用dtx的父类finery的show,调用dtx的输入(也就是kk)的show函数,打印出垮裤,然后又调用kk的父类finery的show,调用kk的输入(也就是pqx)的show函数,打印出破球鞋,然后调用pqx的父类finery的show,调用pqx的输入(也就是xc)的show函数,打印出装扮的小菜。
装扮模式的好处之一就是能有选择地、顺序随意地进行装饰。实际使用的例子如,参数过滤和数据加密,两者都算是装饰器(在原来的基础上进行小修改)。这些装饰只在一部分特定的时候需要,将它们分离出来,以免在核心类中包含过多各种特殊情况下的装饰代码。
18.代理模式
书中的例子举得特别好,追女生时托人代送礼物。如图:
subject可以定义送礼物的接口,realsubject表示礼物真正的来源(隔壁班的男生),proxy表示代理,也有送礼物的功能,但是礼物是subject(也就是追求者)买的。
代理模式的用途我只看懂了一部分:
- 虚拟代理,比如浏览器使用代理模式来优化下载,用未加载好的图片框代替图片
- 安全代理,在访问真实对象前检查权限
- 智能指引,比如统计真实对象的引用数,在访问真实对象前检查锁
19.工厂方法模式
工厂方法克服了简单工厂违背“开放-封闭”原则的缺点(后者在添加新运算的时候除了要实现运算类,还需要修改工厂类中的条件分支),不过仍然封装了对象的创建过程。
我个人认为,工厂方法和简单工厂的区别在于,前者有很多种工厂,每种工厂只有一种图纸,这样增加图纸时直接增加新的图纸类和工厂类即可;而后者只有一个工厂,增加图纸时需要增加新的图纸类,并修改唯一的工厂类。即新增与修改的区别。
具体选择什么工厂,需要在客户端实现。个人认为,工厂方法适合各类都存在且都有一定数量、且需要修改时,此时如果还将选择字符串传入工厂,那修改时就要在客户端中修改很多次选择字符串。
上图是简单工厂模式,“学雷锋的大学生”就是选择字符串。
20. 原型模式
例子举得生动有趣,批量投简历。
需要提醒一下, 用对象赋值对象传的是引用,内存中只有一个副本。
笨版本:
可以看出,要几份简历,就需要实例化几次,并且如果想批量修改也十分麻烦。
更好的版本:
实现clone接口即可实现原型模式。
clone有浅复制和深复制的问题。如果成员中有对象,对其浅复制还是深复制,如果深复制,复制几层(此对象可能也有对象类型的成员变量,多层嵌套)?如果出现循环嵌套怎么办?有网友说,可以通过序列化的方法实现深克隆。
clone除了能减少实例化次数从而代码量,个性化复印,还能提高性能,因为不需要反复调用初始化方法,而是直接复制被clone的对象。
21.模板方法模式
举的例子是老师想考考学生,比较差的方法是让学生抄一遍题目,并写上自己的答案。比较好的方法是,印很多份一样的试卷。这样一是不容易抄错,二是如果想修改试题只需要修改一次。
其实模板方法模式就是基本的继承思想,即将不变行为放到父类,来实现代码的复用。
22.迪米特法则
其实就是封装和松耦合的基本思想,如果两个类不必彼此直接通信,那么它们就不应该发生直接的相互作用。可以通过第三者转发调用。
文章举的例子是,各部门找IT部门办事。最好是大家都直接找主管,由主管进行调度,这样大家就不需要知道IT部里每个人的细分职责、每个人目前有没有空。这个主管就是那个第三者。这样IT部门就实现了封装,而主管就是IT部的对外接口。
23.外观模式
书中举的例子是炒股票和买基金,炒股票是高耦合,买基金则是低耦合(投资者和股票通过基金经理交互)。
代理模式与外观模式的区别:
代理对象代表单一对象,
外观对象代表一个子系统,
代理的客户无法直接访问目标,
外观的客户可以直接访问子系统中的对象,但通常由外观对象提供对子系统各元件功能的简化的共同层次的调用接口。
适配器模式也用于衔接。适配器复用已有的接口。而外观提供新的、更方便的接口。
24.建造者模式
文章举的例子是,炒面和炒饭品质是不稳定的,而肯德基麦当劳的品质则比较稳定。
感觉建造者模式有两个要素,一是用了抽象类做父类(personBuilder),这保证了流程的标准化;二是director的存在,这保证了调用者无需知道细节。
抽象类不难理解,关键是如果没有director,那么使用者就需要了解所有需要实现的方法(在这个例子中,使用者就需要知道人有两手两脚一头等细节)。而有了director之后,使用者只需要知道有两种人,胖人和瘦人。在实际使用中,我常常遇到这种只需要指定枚举常数的接口(比如,写图形界面时,只需要指定layout类型即可生成一个layout实例)。
建造者模式的好处是,用户只需要知道自己想要的产品的名字,而不需要知道其建造过程和细节。
25.观察者模式
书中举的例子是前台通知员工们关闭股票。
又叫订阅-发布模式。
主题对象在发生变化时,会通知所有观察者对象,使观察者更新。
主题对象需要维护一个观察者队列。观察者需要能够读懂主题对象状态的变化。
当一个对象的改变需要同时改变其它多个对象,且不知道具体有多少对象有待改变的时候,可以使用观察者模式。
观察者模式实现了松耦合的类间协作,主题和观察者都依赖于接口,而不是互相依赖(依赖倒转)。
26.抽象工厂模式
书中举的例子是同一份代码要写sql server和access两个版本。
如果只有一个user表,那么应用了工厂方法模式的客户端代码如下:
客户端只需要选择factory的具体类型即可,然后用一句IUser iu=factory.createUser()即可创建正确的对象。这样客户端的改动就尽可能少了。
在之前简单工厂和工厂方法中,factory只生产一种产品(比如矿泉水,工厂方法中有两个factory,一个生产农夫山泉,一个生产怡宝,简单工厂中只有一个厂既能生产农夫山泉又能生产怡宝)。本例中,如果不止user一张表的话,factory就要生产两种关系不大的产品(比如矿泉水和汉堡包。本例中是user表和department表)。
抽象工厂模式的好处是,我只需要更换工厂的名称,就能更换整个产品系列(此例中,可以轻松添加和切换数据库品牌)。此外,它还保留了工厂模式的优点,即客户端只需要做最小的修改,与具体图纸松耦合。
抽象工厂模式的坏处是,如果需要增加产品系列的内容,需要修改的类会很多(此例中,再添加新的表就比较麻烦了)。
可以说,成也产品系列,败也产品系列。
为了开放-封闭而提出工厂方法,但也导致需要在客户端选择工厂,这里有取舍。
简单工厂不需要在客户端选择工厂,但需要在工厂内用switch选择图纸,违反了开放-封闭。
兼顾的方法是简单工厂+反射,这样既不用在客户端中选择工厂,也不需要在工厂内使用switch。
从这个例子中可以看出,其实抽象工厂和简单工厂/工厂方法是可以叠加的,也就是说,其实只有后两者是并列关系。抽象工厂的判定标准不太一样。
27.状态模式
书中举的例子是,加班者在一天中的不同时间有不同的工作状态(需要对当前时间进行冗长的条件判断)。
把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。
简单地说,就是用来消除庞大的条件分支语句。
经状态模式改造后的本例:
实际实现时其实非常像switch,都是按顺序的,如果入参超出了当前类指定的范围,则通过setstate跳转到另一个状态,并且再次执行writeprogram函数。
翻译者类中,有一个外籍中锋成员变量。翻译者的attack实际上是调用了外籍中锋的进攻。这样就实现了接口转接。
适配器模式适用于双方都不太容易修改(否则更好的做法是统一接口),但又需要一起使用的情况。
28.备忘录模式
书中举的例子是游戏存档。
目标是在类外部保存类的状态,且不要破坏封装(即,不知道类内有何属性,也能备份之)
图中菱形+箭头表示聚合。caretaker中可能有多个memento。
memento可以只包含originator的部分信息。
29.组合模式
妙处在于,composite继承自component,同时composite组成component。就如树枝由树枝组成,很有递归的感觉。
用户可以无区别对待单个对象(leaf)和组合对象(composite)。
比如买电脑的商家,可以卖整机也可以卖配件;
比如复制文件,可以复制单个文件,也可以复制整个文件夹;
比如文本编辑,可以加粗单个字,也可以加粗一个自然段。
以及书中举的例子,应用于有多级分公司的企业的OA系统。
30.迭代器模式
常见高级语言都已经在常用语法上支持了,所以很少使用此模式。
在不暴露集合的内部结构的情况下,使外部能够访问集合内部的数据。
31.单例模式
保证一个类仅有一个实例,由类自身负责保证。且类要提供该实例的访问方法。
书中举的例子是,图形界面开发时点击菜单栏中的工具箱,点击多次也应该只出现一个。
实现的要点就是图中的气泡框。
实用类(如Math类)也会采用私有化构造函数的方法来避免其有实例。
实用类是一些静态方法和静态属性的集合(仍然以Math为例),没有状态,不被子类继承;
单例类有状态,可以被子类继承。
多线程下的单例模式(双重锁定):
如果不为空,则根本不需要加锁,这样可以减少加锁的开销(相比先锁再判断是否为空)。因此,外层的判断是为了减少不必要的加锁。
考虑到两个线程以较小间隔通过第一重判断,如果内层不再判断,就会创建出两个实例。因此,内层的判断是为了避免多个实例。
单例类分为饿汉式单例类和懒汉式单例类。
前者在类加载时就实例化,需要提前占用系统资源;
后者在第一次被引用时实例化,面临多线程安全问题,需要双重锁定。
32.桥接模式
首先,桥接模式是合成/聚合复用原则的体现。优先使用合成/聚合,然后再考虑类继承。这样能保持类和类继承保持较小的规模(从下面三图可以看出)。继承不应乱用,最好是 is a 的时候再继承。
可以看出,手机系统有品牌和软件两种分类标准,但无论采取哪种分类标准,膨胀的速度都很快。而图三则明显更简单。
桥接模式适合的场景是,实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
33.命令模式
书中举的例子是烧烤摊vs烧烤店,烧烤店中有服务员,实现了顾客和烤肉师傅的解耦,并且可以对命令进行排队,而且可以提供订单取消(顾客想取消)和订单否认(师傅因为缺料等原因无法完成),还可以提供日志服务。
在下图中,服务员就相当于invoker,receiver就相当于烤肉师傅,concrete command有烤羊肉串和烤鸡翅。command内有一个成员变量receiver,这样就可以通过调用receiver的方法来execute。
34.职责链模式
书中举的例子是小菜向经理-人资总监-总经理提出请假/加薪请求。
职责链模式使多个对象都有机会处理请求,从而避免请求的发送者与接收者的耦合关系(简化了对象之间的相互连接)。将这些对象连成一条链,沿着链传递该请求,直到有一个对象处理它为止。
请求者不用管具体是哪一个handler响应,handler之间也并不互相了解(除了自己的后继)。
而且handler的增删也比较简便。
在客户端中需要给每一个handler设置后继,实际处理请求的时候总是交给第一个handler。
35.中介者模式
尽管将一个系统分割成许多对象通常可以增加其复用性,但是对象间相互连接的激增又会降低其复用性。大量的连接使得一个对象需要其它对象的支持才能完成工作,系统表现为一个不可分割的整体,不易进行改动。
中介者模式是迪米特原则的体现。通过中介者模式,可以将系统的网状结构(下图一)变为以中介者为中心的星型结构(下图二)。
书中以联合国为中介者的实例。
中介者的存在,降低了诸对象的耦合程度。
每一个colleague实例中,都有一个mediator作为成员变量。
mediator必须认识所有colleagues。
中介者模式的优点和缺点都在于集中控制。中介者以自身的复杂为代价,实现了其它对象的解耦。
36.享元模式
利用共享支持大量细粒度的对象。
比如内容相同的字符串,可以利用引用来共享;一盘围棋中的所有黑棋,也可以共享(颜色作为内部状态共享,位置作为外部状态不共享)。
应用程序中的对象数量众多、内存开销巨大的时候,可以考虑使用享元模式。
书中举的例子是小菜帮多位用户做产品展示和博客两类网站,同类网站其实可以共享核心代码和数据库(flyweight的内部状态),不同的只是账号(外部状态)。
37.解释器模式
应用例子如浏览器、正则表达式,或是机器人的控制。
当有一个语言需要执行,且可以将该语言的句子表示为抽象语法树的时候,可以使用解释器模式。
用迷你语言来表达程序要解决的问题,以迷你语言写成迷你程序来表现具体的问题。
书中的例子是音乐解释器。
上图中,音符、音阶、音速都 is a 表达式,具体表达式种类的判断在客户端中用switch实现(以字母为key,数字为value),种类确定后执行该种类的execute方法即可。
演奏内容就是context,在本例中是上海滩的自定义语言版本。
解释器模式的优势在于,文法容易扩展和修改。
38.访问者模式
书中举的例子是,男人和女人在不同事情发生时做出不同的反应。
访问者模式适合数据结构不变,而算法/操作多变的情况。其优势在于可以轻松添加/删除一个visitor。
从上图中可以看出,如果element的元素个数发生变化,每个visitor都要修改,所以只有当数据结构,也就是element不变的时候,才使用访问者模式。
element是人,concrete element是男人和女人(因此element不变);
object structure是element的容器(实现attach和detach方法用于容器增删),方便visitor遍历所有的element;
visitor是各种各样的操作,或者任何能引起变化的动作;
concrete visitor如成功、失败、恋爱、结婚,它们会引起element的变化。
双分派,因为element的变化同时由element实例(男/女)和visitor实例(成功/失败)决定,所以需要将visitor作为参数传给element,再由element调用适合自己的visitor的具体方法。
39.设计模式总结
设计模式可以分为三类,创建型、结构型和行为型。括号内为我个人的助记符,可以忽略。
创建型:简单工厂(计算器),工厂方法(计算器,客户端不知道所用的对象是哪一个具体对象),抽象工厂(sql server和access),单例(工具箱),原型prototype(简历海投),建造者(炒面与炒饭,画小人,layout)
通常,从工厂方法开始,如果需要更多灵活性,则可能演化为抽象工厂、原型、建造者。
结构型:适配器(姚明,不同数据库),桥接(华为备忘录),组合composite(套娃分公司),装饰decorator(连续穿衣服),外观(基金与股票),享元(批量建站,围棋),代理(代送礼物)
行为型:观察者(前台报信),命令(烧烤店),职责链(请假加薪),模板(较为简单,本文未收录,将可重复代码提炼到抽象类中),状态(加班状态),解释器(音乐解释器),中介者(联合国),访问者(男女有别),备忘录(不破坏封装,游戏存档),迭代器,策略(打折收银,利用context类减少算法使用者和算法之间的耦合),
最后
以上就是欢呼面包为你收集整理的java思想 设计模式 《大话设计模式》知识总结1.封装2.框架+数据3.抽象,抽象类,接口4.MVC设计模式5.继承6.多态7.从活字印刷看面向对象的好处8.耦合9.业务与界面分离10.UML类图11.简单工厂模式12.策略模式13.单一职责原则14.开放-封闭原则15.里氏代换原则16.依赖倒转原则17.装饰模式18.代理模式19.工厂方法模式20. 原型模式21.模板方法模式22.迪米特法则23.外观模式24.建造者模式25.观察者模式26.抽象工厂模式27.状态模式28.备忘录模式29.的全部内容,希望文章能够帮你解决java思想 设计模式 《大话设计模式》知识总结1.封装2.框架+数据3.抽象,抽象类,接口4.MVC设计模式5.继承6.多态7.从活字印刷看面向对象的好处8.耦合9.业务与界面分离10.UML类图11.简单工厂模式12.策略模式13.单一职责原则14.开放-封闭原则15.里氏代换原则16.依赖倒转原则17.装饰模式18.代理模式19.工厂方法模式20. 原型模式21.模板方法模式22.迪米特法则23.外观模式24.建造者模式25.观察者模式26.抽象工厂模式27.状态模式28.备忘录模式29.所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复