设计模式(Design Pattern,DP)
背景
“设计模式”这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 直到 1990 年,软件工程界才开始研讨设计模式的话题。 1995年,“四人组”(Gang of Four,GoF)合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,在书籍中收录了 23 个设计模式,这是设 计模式领域里程碑的事件,导致了软件设计模式的突破。 直到今天,狭义的设计模式还是该书中所介绍的23种经典设计模式。
概念
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、 代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它 是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提 高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的六大设计原则
开闭原则:Open Closed Principle,OCP
定义:
一个软件实体应 该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。(对扩展开放,对修改关闭)
软件实体包括以下几个部分:
·项目中划分出的模块
·类与接口
·方法
一个软件产品在她的生命周期内一般都会发生变化,开闭原则视为软件实体的未来事件而制定的对现行开发设计进 行约束的一个原则。
案例:
public interface IBook {
public String getName();
public int getPrice();
public String getAuthor();
}
package com.java.principle.ocp;
//import com.java.Design.IBook;
public class NovelBook implements IBook{
private String name;
private int price;
private String author;
public NovelBook(String name, int price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getPrice() {
return this.price;
}
@Override
public String getAuthor() {
return this.author;
}
}
import com.java.principle.ocp.IBook;
import com.java.principle.ocp.NovelBook;
import java.text.NumberFormat;
import java.util.ArrayList;
public class BookStore {
private final static ArrayList<IBook> bookList=new ArrayList();
static {
bookList.add(new NovelBook("红楼梦", 9900, "曹雪芹 "));
bookList.add(new NovelBook("侠客行", 8900, "金庸 "));
bookList.add(new NovelBook("原则", 6900, "瑞·达利欧"));
bookList.add(new NovelBook("海贼王", 4900, "尾田荣一郎"));
}
public static void main(String[] args) {
NumberFormat format=NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(2);
System.out.println("-------------书店卖书记录如下----------------------");
for (IBook book : bookList) {
System.out.println("书籍名称:"+book.getName()+"\t\t作者:"+book.getAuthor()+"\t\t价 格:"+format.format(book.getPrice()/100.0)+"元");
}
}
}
这里是一个简单的实现书店功能的一个代码,但是如果这里需要对代码功能进行修改,比如对价格进行打折处理,按照开闭原则尽量不能对代码进行修改。我们有三种方法:
(1)修改接口
这样直接修改接口,添加一些抽象方法,他的实现类,以及子类,main等都会报错,都需要修改十分的麻烦,不符合开闭原则。
(2)修改实现类
修改实现类虽然方便,针对用户显示没有问题,但是购买书籍的人员进行修改却也显示为打折的价格,这样就存在问题。
(3)通过扩展实现变化
增加一个子类OffNovelBook,覆写getPrice方法,高层次的模块(也就是BookStore中static静态块中)通过 OffNovelBook类产生新的对象,完成业务变化对系统的最小开发。这样修改也少,风险也小。
package com.java.principle.ocp;
public class OffNovalBook extends NovelBook{
public OffNovalBook(String name, int price, String author) {
super(name, price, author);
}
@Override
public int getPrice(){
int sellPrice = super.getPrice();
int offPrice = 0;
if(sellPrice>7000){
offPrice = sellPrice*90/100;
}
else{
offPrice = sellPrice*80/100;
}
return offPrice;
}
}
static {
bookList.add(new OffNovalBook("红楼梦", 9900, "曹雪芹 "));
bookList.add(new OffNovalBook("侠客行", 8900, "金庸 "));
bookList.add(new OffNovalBook("原则", 6900, "瑞·达利欧"));
bookList.add(new OffNovalBook("海贼王", 4900, "尾田荣一郎"));
}
这样再在主类中修改添加的类名,就可以实现该功能。
作用:
- 对软件测试的影响
软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运 行。 - 可以提高代码的可复用性
粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。 - 可以提高软件的可维护性
遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。
单一职责原则:Single responsibility principle,SRP
不能让一个对象有太多的职责,功能。如果一个对象承担了太多的责任,就会:
- 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
- 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码 的浪费
案例:
package com.java.principle.srp;
public interface IPhone{
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂断电话
public void hangup();
}
修正后的案例:
package com.java.principle.srp;
public interface IPhone{
}
package com.java.principle.srp;
public interface IDataTransfer extends IPhone{
//通话
public void chat(IConnectionManager con);
}
package com.java.principle.srp;
public interface IConnectionManager extends IPhone{
//拨通电话
public void dial(String phoneNumber);
//通话完毕,挂断电话
public void hangup();
}
修正之后就是将一个接口拆分成3个,实现了单一原则。
作用:
·降低类的复杂度。
·一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
·提高类的可读性。
·复杂性降低,自然其可读性会提高。
·提高系统的可维护性。可读性提高,那自然更容易维护了。
·变更引起的风险降低。
·变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可·以显著降低对其 他功能的影响。
PS:单一职责同样也适用于方法。一个方法应该尽可能做好一件事情。如果一个方法处理的事情太多,其颗粒度会 变得很粗,不利于重用。
人是活的,人不能被尿憋死。不一定要所有都完全遵守原则,要灵活变通。
里氏替换原则:Liskov Substitution Principle,LSP
子类可以替换父类。
继承的优缺点:
优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性;
- 提高代码的可扩展性,子类可形似于父类,但异于父类,保留了自己独特的个性;其实很多开源框架的扩展都 是通过继承父类实现的。
- 提供产品或者项目的开放性。
缺点:
- 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性;
- 降低了代码的灵活性。子类必须拥有父类的属性和方法,让子类中多了约束
- 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能 会造成非常糟糕的结果,要重构大量的代码。
定义:
- 在一个程序中,如果可以将一个类T的对象全部替换为另一个类S的对 象,而程序的行为没有发生变化,那么S是T的子类。
- 任何一个使用父类的地方,你都可以把它替换成它的子类,而不会发生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必可以替换。
里氏替换原则是继承复用的基石,它为良好的继承定义了一个规范,定义中包含了4层含义:
1. 子类必须完全实现父类的方法。
实例:
package com.java.principle.lsp;
//枪之抽象类
public abstract class AbstractGun {
//射击
public abstract void shoot();
}
package com.java.principle.lsp;
//手枪,便于携带,但是射程8行
public class HandGun extends AbstractGun{
@Override
public void shoot() {
System.out.println("手枪之射击-----");
}
}
package com.java.principle.lsp;
//步枪:威力猛,皮还厚
public class RifleGun extends AbstractGun{
@Override
public void shoot() {
System.out.println("步枪射击-----");
}
}
package com.java.principle.lsp;
//机枪:威力大,射速快,连续射击
public class MachineGun extends AbstractGun{
@Override
public void shoot() {
System.out.println("机枪射击-----");
}
}
package com.java.principle.lsp;
//士兵来使用枪的角色
public class Soilder {
//士兵用的枪
private AbstractGun gun;
//给士兵哥哥配枪
public void setGun(AbstractGun gun){
this.gun = gun;
}
public void killEnemy(){
System.out.println("士兵杀敌:");
gun.shoot();
}
}
package com.java.principle.lsp;
public class Client {
public static void main(String[] args) {
//定义士兵
Soilder s = new Soilder();
s.setGun(new HandGun());
s.killEnemy();
}
}
结果图:
要点:子类实现父类的所有方法。
2. 子类中可以增加自己特有的方法。
案例:
在上述代码的基础上增加了:
package com.java.principle.lsp;
//狙击步枪,有镜子之
public class AUG extends RifleGun{
//狙击步枪有精准的瞄准镜
public void zoomOut(){
System.out.println("通过四倍镜观察敌人:");
}
@Override
public void shoot() {
System.out.println("AUG射击之-----");
}
}
package com.java.principle.lsp;
public class Snipper extends Soilder{
public void killEnemy(AUG aug) {
//通过镜子观察
aug.zoomOut();
aug.shoot();
}
}
package com.java.principle.lsp;
public class Client {
public static void main(String[] args) {
//定义士兵
Soilder s = new Soilder();
s.setGun(new HandGun());
s.killEnemy();
//定义狙击手
Snipper s1 = new Snipper();
//给狙击手配枪
s1.setGun(new AUG());
s1.killEnemy();
}
}
运行结果图:
如果这里将子类用父类替换则会报错。
s1.setGun((AUG)(new RifleGun()));
3.当子类覆盖或实现父类的方法时,方法的输入参数(方法的形参)要比父类方法的输入参数更宽松
package com.java.principle.lsp;
import java.util.HashMap;
import java.util.Map;
public class LSP {
//当子类覆盖,重写,父类的方法的时候,方法输入参数要比父类方法输入的参数范围更大
class Parent{
public void fun(HashMap map) {
System.out.println("父类执行-----");
}
}
class Sub extends Parent{
public void fun(Map map) {
System.out.println("子类执行-----");
}
}
public static void main(String[] args) {
System.out.println("父类的结果:");
LSP lsp = new LSP();
Parent p = lsp.new Parent();
HashMap hashMap = new HashMap();
p.fun(hashMap);
//父类可以被子类替换
System.out.println("子类替换后的结果:");
Sub s = lsp.new Sub();
s.fun(hashMap);
}
}
运行结果:
要点:当子类覆盖,重写,父类的方法的时候,方法输入参数要比父类方法输入的参数范围更大
4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
代码部分和上部分相似:
package com.java.principle.lsp;
import java.util.HashMap;
import java.util.Map;
public class LSP1 {
//当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
abstract class Parent{
public abstract Map fun();
}
class Sub extends Parent{
@Override
public HashMap fun() {
HashMap map = new HashMap();
map.put("b","子类被执行");
return map;
}
}
public static void main(String[] args) {
LSP1 lsp = new LSP1();
Parent p = lsp.new Sub();
HashMap hashMap = new HashMap();
System.out.println(p.fun());
}
}
运行结果图:
如果反过来:
package com.java.principle.lsp;
import java.util.HashMap;
import java.util.Map;
public class LSP1 {
//当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
abstract class Parent{
public abstract HashMap fun();
}
class Sub extends Parent{
@Override
public Map fun() {
HashMap map = new HashMap();
map.put("b","子类被执行");
return map;
}
}
public static void main(String[] args) {
LSP1 lsp = new LSP1();
Parent p = lsp.new Sub();
HashMap hashMap = new HashMap();
System.out.println(p.fun());
}
}
编译无法通过。
要点:
作用:
- 里氏替换原则是实现开闭原则的重要方式之一。
- 它克服了继承中重写父类造成的可复用性变差的缺点。
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
- 里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
- 如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
- 如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。
依赖倒置原则:Dependence Inversion Principle,DIP
定义:
-
高层模块不应该依赖低层模块,两者都应该依赖其抽象;
-
抽象不应该依赖细节,
-
细节应该依赖抽象
核心思想:要面向接口编程,不要面向实现编程。
实例代码:
package com.java.principle.dip;
public interface ICar {
void run();
}
package com.java.principle.dip;
public interface IDriver {
//驾驶车,通过传入接口实现抽象依赖关系
void drive(ICar car);
}
package com.java.principle.dip;
//司机实现司机接口
public class Driver implements IDriver{
public void drive(ICar car){
System.out.println("司机在开车.");
car.run();
}
}
package com.java.principle.dip;
//宝马车
public class BMW implements ICar{
@Override
public void run(){
System.out.println("宝马车在飞驰之.");
}
}
package com.java.principle.dip;
import com.java.principle.ocp.IBook;
//奔驰车
public class Benz implements ICar {
@Override
public void run(){
System.out.println("奔驰车在跑.");
}
}
package com.java.principle.dip;
//属于高层模块,高层业务逻辑,对底层的依赖都建立在抽象上面
public class Client {
public static void main(String[] args) {
Driver d = new Driver();
Benz b = new Benz();
BMW bmw = new BMW();
d.drive(bmw);
}
}
运行结果图:
按照现实生活中,司机是可以开各种各样的车的,所以在抽象中,我们具体的车就需要依赖于顶层的接口ICar这个类,进行细节上的实现。
具体的实现方法
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 尽量不要覆写基类的方法
- 使用继承时结合里氏替换原则。
作用:
- 依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则可以提高系统的稳定性。
- 依赖倒置原则可以减少并行开发引起的风险。
- 依赖倒置原则可以提高代码的可读性和可维护性。
接口隔离原则:Interface Segregation Principle,ISP
定义:
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它 的类去调用。
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同 的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
实例代码:
package com.java.principle.isp;
public interface IpettyGirl {
void goodLooking();
void niceFigure();
void greatTemperament();
}
package com.java.principle.isp;
public class PettyGirl implements IpettyGirl{
private String name;
public PettyGirl(String name){
this.name = name;
}
@Override
public void goodLooking() {
System.out.println(this.name+"脸好看.");
}
@Override
public void niceFigure() {
System.out.println(this.name+"身材无敌.");
}
@Override
public void greatTemperament() {
System.out.println(this.name+"气质好.");
}
}
package com.java.principle.isp;
public abstract class AbstractSearcher {
public IpettyGirl girl;
public AbstractSearcher(IpettyGirl girl1){
this.girl = girl;
}
public abstract void seqarch();
}
package com.java.principle.isp;
public class Searcher extends AbstractSearcher {
public Searcher(IpettyGirl girl) {
super(girl);
}
@Override
public void search() {
System.out.println("找到的美女如下:");
super.girl.goodLooking();
super.girl.niceFigure();
super.girl.greatTemperament();
}
}
package com.java.principle.isp;
public class Client {
//定义美女
IpettyGirl reBa = new PettyGirl("迪丽热巴");
AbstractSearcher searcher = new Searcher(reBa);
searcher.search();
}
但是一个人如果气质很好,身材与长相平平,那也可以称之为美女,这个时候代码无法完成需求,所以则需要将接口进行拆分
package com.java.principle.isp;
public interface IGoodBodyGirl {
void goodLooking();
void niceFigure();
}
package com.java.principle.isp;
public interface IGreatTempermentGirl {
void greatTemperment();
}
这样就可以完成。
具体的实现方法:
- 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
- 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
- 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
作用:
- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫 设计冗余的代码。
迪米特法则:Law of Demeter,LoD
定义:
它要求一个对象应该对其他对象有最少的了解。通俗的说,一个类应该对自己需要耦合或调用的类知道的最少,被耦合或调用的类的内部是如何复杂都与我无关,我就知道你提供的public方法就好。
只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
朋友:
- 当前对象本身(this)
- 当前对象的方法参数(以参数形式传入到当前对象方法中的对象)
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
- 当前对象所创建的对象
演示代码:
package com.java.principle.lod;
public class Client {
public static void main(String[] args) {
Agent agent = new Agent();
agent.setStar(new Star("吴京"));
agent.setFans(new Fans("小李"));
agent.meeting();
agent.setCompany(new Company("中央电视台"));
agent.business();
}
}
class Agent{
private Star star;
private Fans fans;
private Company company;
public void meeting(){
System.out.println(this.fans.getName()+"与明星"+this.star.getName()+"见面了.");
}
public void business(){
System.out.println(this.company.getName()+"与明星"+this.star.getName()+"合作了.");
}
public void setCompany(Company company) {
this.company = company;
}
public void setFans(Fans fans) {
this.fans = fans;
}
public void setStar(Star star) {
this.star = star;
}
}
class Star{
private String name;
public Star(String name){
this.name = name;
}
public String getName() {
return name;
}
}
class Fans{
private String name;
public Fans(String name){
this.name = name;
}
public String getName(){
return name;
}
}
class Company{
private String name;
public Company(String name){
this.name = name;
}
public String getName(){
return name;
}
}
运行截图:
明星与粉丝,明星与公司,都是通过经纪人这个“朋友”进行交流。
优点:
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
实现方法:
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
注意:
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
设计模式具体之
设计模式分为创建型,行为型,结构型。
创建型
定义:
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
单例模式
概念:
中国自从秦始皇确立了皇帝这个职位之后,同一个时期基本上上就只有一个人孤零零的坐在皇位上啦。这种情况的 好处就是大家好办事,大家讨论或者汇报大事的时候只要提及皇帝,每个人都知道指的是谁,不需要在皇帝面前加 上特定的称呼。这种过程反应到软件设计领域就是:一个类只能产生一个对象(皇帝),大家对他的依赖都是相同 的。我们把皇帝这种特殊的职位通过程序来实现。
代码实例:
package com.java.dp.singleton;
public class Emperor {
private static Emperor emperor = null;
//构造函数私有,避免在外面随意创建
private Emperor(){}
//提供产生实例的方法
public static Emperor getInstance(){
if(emperor==null){
emperor = new Emperor();
}
return emperor;
}
public void work(){
System.out.println("我是大王康曦,有事说事.");
}
}
package com.java.dp.singleton;
//大臣
public class Minister {
public static void main(String[] args) {
for(int i = 1;i <= 7;i++){
Emperor emperor = Emperor.getInstance();
System.out.print("第"+i+"天:");
emperor.work();
}
}
}
运行结果:
核心操作:对构造函数进行私有,外部无法创造实例对象,只能通过内部进行创建。
该代码存在线程不安全的情况,如果线程A刚好进行到判断的时候,B创建完成,就会出现不安全的问题。
解决代码:
1.加关键词synchronized
2.加载类直接创建
package com.java.dp.singleton;
public class Emperor {
private static Emperor emperor = new Emperor();
//构造函数私有,避免在外面随意创建
private Emperor(){}
//提供产生实例的方法
public static Emperor getInstance(){
return emperor;
}
public void work(){
System.out.println("我是大王康曦,有事说事.");
}
}
使用单例模式场景:
-
某类只要求生成一个对象的时候,如一个航班的机长、每个人的身份证号等。
-
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
-
当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
-
在计算机系统中, Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、
-
打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应
-
用程序中的对话框、系统中的缓存等常常被设计成单例。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
-
单例模式一般没有接口,扩展很困难。如果要扩展,只能修改代码。
-
与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
若需要取得几个对象,可通过内部创建ArrayList进行存储。
package com.java.dp.singleton;
import java.util.ArrayList;
import java.util.Random;
public class Emperor {
private String name;
private static final int maxNum = 2;
private static ArrayList<Emperor> list = new ArrayList<>(2);
static {
list.add(new Emperor("皇帝:康熙"));
list.add(new Emperor("皇帝:乾隆"));
}
private static Emperor emperor = new Emperor();
//构造函数私有,避免在外面随意创建
private Emperor(){}
private Emperor(String name){
this.name = name;
}
//提供产生实例的方法
public static Emperor getInstance(){
int index = new Random().nextInt(maxNum);
return list.get(index);
}
public void work(){
System.out.println("我是大王"+name+",有事说事.");
}
}
工厂方法模式
定义:
定义一个用户创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
使用场景:
- 客户只知道创建产品的工厂名,而不知道具体的产品名。 如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
代码模板:
//抽象产品:提供了产品的接口
public interface Product{
public void method();
}
//具体的产品可以有多个,都实现抽象产品接口
public class ConcreteProduct1 implements Product{
public void method(){
//具体业务逻辑处理,例如
System.out.println("具体产品1显示...");
}
}
public class ConcreteProduct2 implements Product{
public void method(){
//具体业务逻辑处理,例如
System.out.println("具体产品2显示...");
}
}
//抽象工厂:负责定义产品对象的产生
public abstract class AbstractFactory{
//创建一个产品对象,输入的参数类型可以自行设置
public abstract <T extends Product>T createProduct(Class<T> tClass);
}
//具体工厂:具体如何生产一个产品的对象,是由具体的工厂类实现的
public class ConcreteFactory implements AbstractFactory{
public <T extends Product> T createProduct(Class<T> tClass) {
Product product=null;
try {
product=(T)Class.forName(tClass.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T)product;
}
}
//场景类:
public class Client {
public static void main(String[] args) {
AbstractFactory factory=new ConcreteFactory();
Product product=factory.createProduct(ConcreteProduct1.class);
//继续其他业务处理
}
}
面包接口:
package com.java.dp.factory;
public interface bread {
//每个面包都有颜色
void getColor();
}
北极熊面包:
package com.java.dp.factory;
public class PolarBearBread implements bread{
@Override
public void getColor() {
System.out.println("烤的时间短了---北极熊面包");
}
}
黑熊面包:
package com.java.dp.factory;
public class BlackBearBread implements bread{
@Override
public void getColor() {
System.out.println("烤的时间长了---黑熊面包");
}
}
布朗熊面包:
package com.java.dp.factory;
public class BrownBearBread implements bread{
@Override
public void getColor() {
System.out.println("烤的时间OK---布朗熊面包");
}
}
抽象面包创建工厂:
package com.java.dp.factory;
public abstract class AbstractBreadFactory {
public abstract bread createBread(Class cls);
}
具体面包创建工厂:
package com.java.dp.factory;
public class BreadFactory extends AbstractBreadFactory{
@Override
public bread createBread(Class cls) {
bread b = null;
try {
b = (bread) Class.forName(cls.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
System.out.println("出错了.");
}
return b;
}
}
大厨:
package com.java.dp.factory;
public class Chef {
public static void main(String[] args) {
//烤箱OK
AbstractBreadFactory abf = new BreadFactory();
//1
System.out.println("厨师第一次烤");
bread polorBearBread = abf.createBread(PolarBearBread.class);
polorBearBread.getColor();
//2
System.out.println("厨师第二次烤");
bread blackBearBread = abf.createBread(BlackBearBread.class);
blackBearBread.getColor();
//3
System.out.println("厨师第三次烤");
bread brownBearBread = abf.createBread(BrownBearBread.class);
brownBearBread.getColor();
}
}
运行结果:
抽象工厂模式
抽象工厂模式和工厂模式很相似,主要的区别还是生产多种类的产品。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
定义:
为创建一组相关或者相互依赖的对象提供一个接口,而且无须指定他们 的具体类。
通用代码:
//抽象产品类(只写了一个AbstractProductA,AbstractProductB省略):
public abstract class AbstractProductA{
//每个产品的共有方法
public void sharedMthod(){
}
//每个产品相同方法,不同实现
public abstract void doSomething();
}
具体产品类:
public class ProductA1 extends AbstractProductA{
public abstract void doSomething(){
System.out.println("产品A1的实现方法");
}
}
public class ProductA2 extends AbstractProductA{
public abstract void doSomething(){
System.out.println("产品A2的实现方法");
}
}
//抽象工厂类:
public abstract class AbstractCreator{
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProductB();
//如果有N个产品族,该类中应该有N个创建方法
}
//产品等级实现类:
//有M个产品等级就应该有M个工厂的实现类,在每个实现工厂中,实现不同产品族的生产业务。
public class Creator1 extends AbstractCreator{
//只生成产品等级为1的A产品
public AbstractProductA createProductA(){
return new ProductA1();
}
//只生成产品等级为1的B产品
public AbstractProductB createProductB(){
return new ProductB1();
}
}
public class Creator2 extends AbstractCreator{
//只生成产品等级为2的A产品
public AbstractProductA createProductA(){
return new ProductA2();
}
//只生成产品等级为2的B产品
public AbstractProductB createProductB(){
return new ProductB2();
}
}
//场景类
public class Client{
public static void main(String[]] args){
//定义两个工厂
AbstractCreator creator1=new Creator1();
AbstractCreator creator2=new Creator2();
//产生A1对象
AbstractProductA a1=creator1.createProductA();
//产生A2对象
AbstractProductA a2=creator2.createProductA();
//产生B1对象
AbstractProductA a1=creator1.createProductB();
//产生B2对象
AbstractProductA a2=creator2.createProductB();
//按需求自己实现其他
}
}
实例代码:
package com.java.dp.abstractfactory;
public interface bread {
//每个面包都有颜色
void getColor();
//面包都有不同的馅
void getType();
}
package com.java.dp.abstractfactory;
public interface BreadFactory {
//创建北极熊面包
bread createPolorBearBread();
//创建黑熊面包
bread createBlackBearBread();
//创建布朗熊面包
bread createBrownBearBread();
}
package com.java.dp.abstractfactory;
public abstract class AbstractBlackBearBread implements bread {
@Override
public void getColor() {
System.out.println("烤的时间长了---黑熊面包");
}
}
package com.java.dp.abstractfactory;
public abstract class AbstractBrownBearBread implements bread {
@Override
public void getColor() {
System.out.println("烤的时间OK---布朗熊面包");
}
}
package com.java.dp.abstractfactory;
public abstract class AbstractPolarBearBread implements bread {
@Override
public void getColor() {
System.out.println("烤的时间短了---北极熊面包");
}
}
package com.java.dp.abstractfactory;
public class MangoBlackBearBread extends AbstractBlackBearBread {
@Override
public void getType() {
System.out.println("黑熊面包---芒果馅儿");
}
}
package com.java.dp.abstractfactory;
public class MangoBrownBearBread extends AbstractBrownBearBread {
@Override
public void getType() {
System.out.println("布朗熊---芒果馅儿");
}
}
package com.java.dp.abstractfactory;
public class MangoFactory implements BreadFactory{
@Override
public bread createPolorBearBread() {
return new MangoPolorBearBread();
}
@Override
public bread createBlackBearBread() {
return new MangoBlackBearBread();
}
@Override
public bread createBrownBearBread() {
return new MangoBrownBearBread();
}
}
package com.java.dp.abstractfactory;
public class MangoPolorBearBread extends AbstractPolarBearBread {
@Override
public void getType() {
System.out.println("北极熊---芒果馅");
}
}
package com.java.dp.abstractfactory;
public class AppleBlackBearBread extends AbstractBlackBearBread {
@Override
public void getType() {
System.out.println("黑熊面包---苹果馅儿");
}
}
package com.java.dp.abstractfactory;
public class AppleBrownBearBread extends AbstractBrownBearBread {
@Override
public void getType() {
System.out.println("布朗熊---苹果馅儿");
}
}
package com.java.dp.abstractfactory;
public class AppleFactory implements BreadFactory{
@Override
public bread createPolorBearBread() {
return new ApplePolorBearBread();
}
@Override
public bread createBlackBearBread() {
return new ApplePolorBearBread();
}
@Override
public bread createBrownBearBread() {
return new ApplePolorBearBread();
}
}
package com.java.dp.abstractfactory;
public class ApplePolorBearBread extends AbstractPolarBearBread {
@Override
public void getType() {
System.out.println("北极熊---苹果馅");
}
}
package com.java.dp.abstractfactory;
public class Client {
public static void main(String[] args) {
//Apple馅儿的烤面包
BreadFactory apple = new AppleFactory();
//芒果馅儿的烤箱
BreadFactory mango = new MangoFactory();
//生产之
System.out.println("第一批");
bread b1 = apple.createBlackBearBread();
bread b2 = apple.createBrownBearBread();
bread b3 = mango.createBlackBearBread();
b1.getColor();
b1.getType();
b2.getColor();
b2.getType();
b3.getColor();
b3.getType();
System.out.println("第二批");
bread b4 = mango.createBrownBearBread();
bread b5 = apple.createPolorBearBread();
b4.getColor();
b4.getType();
b5.getColor();
b5.getType();
}
}
运行结果图:
相比于之前的工厂模式,他能够生产出各种各类的种类。当我们需要生产类的属性有各种各样的时候可以采用抽象工厂模式。主要的思想还是抽象类与接口的应用。
优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
应用场景:
- 适合于产品之间相互关联、相互依赖且相互约束的地方
- 需要动态切换产品族的地方
建造者(Builder)模式
定义:
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者 模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组 成部分是不变的,但每一部分是可以灵活选择的。
模式的结构
- 产品(Product)类:它是包含多个组成部件的复杂对象,由具体建造者来创建其各个部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 导演(Director)类:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息
模板代码:
产品(Product)类:
public class Product {
private String partA;
private String partB;
private String partC;
public void setPartA(String partA){
this.partA=partA;
}
public void setPartB(String partB){
this.partB=partB;
}
public void setPartC(String partC){
this.partC=partC;
}
public void doSomething(){
//独立业务处理
}
}
抽象建造者(Builder):
public abstract class Builder{
//创建产品的不同部分,以获取不同产品
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public abstract Product buildProduct();
}
具体建造者:
public class ConcreteBuilder extends Builder{
private Product product=new Product();
public void buildPartA(){
product.setPartA("建造 PartA");
}
public void buildPartB(){
product.setPartA("建造 PartB");
}
public void buildPartC(){
product.setPartA("建造 PartC");
}
//组件一个产品
public Product buildProduct(){
return product;
}
}
指挥者:
public class Director {
private Builder builder;
public Director(Builder builder){
this.builder=builder;
}
//产品构建与组装方法:设置不同的零件,生成不同的产品
public Product constructA(){
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.buildProduct();
}
public Product constructB(){
builder.buildPartB();
builder.buildPartA();
builder.buildPartC();
return builder.buildProduct();
}
}
场景类:
public class Client{
public static void main(String[] args){
Builder builder=new ConcreteBuilder();
Director director=new Director(builder);
Product product=director.construct();
product.doSomething();
}
}
实例代码:
我们需要构建车的类,有各种品牌的车,宝马,奔驰等,每辆车有不同的功能,启动,鸣笛等,有一定的顺序要求。
package com.java.dp.builder;
import java.util.ArrayList;
public abstract class CarBuilder {
public abstract void setSequence(ArrayList<String> sequence);
public abstract CarModel getCarModel();
}
package com.java.dp.builder;
import java.util.ArrayList;
public abstract class CarModel {
private ArrayList<String> sequence = new ArrayList<>();
public void setSequence(ArrayList<String> sequence){
this.sequence = sequence;
}
protected abstract void start();
protected abstract void stop();
protected abstract void alarm();
protected abstract void engineBoom();
public void run(){
for (String s : sequence) {
if("start".equalsIgnoreCase(s)){
this.start();
}else if ("stop".equalsIgnoreCase(s)){
this.stop();
}else if ("alarm".equalsIgnoreCase(s)){
this.alarm();
}else if ("engineBoom".equalsIgnoreCase(s)){
this.engineBoom();
}
}
}
}
package com.java.dp.builder;
public class Benz extends CarModel{
@Override
protected void start() {
System.out.println("奔驰车---启动之");
}
@Override
protected void stop() {
System.out.println("奔驰车---停车之");
}
@Override
protected void alarm() {
System.out.println("奔驰车---鸣笛之");
}
@Override
protected void engineBoom() {
System.out.println("奔驰车---引擎轰鸣之");
}
}
package com.java.dp.builder;
public class BMW extends CarModel{
@Override
protected void start() {
System.out.println("宝马车---启动之");
}
@Override
protected void stop() {
System.out.println("宝马车---停车之");
}
@Override
protected void alarm() {
System.out.println("宝马车---鸣笛之");
}
@Override
protected void engineBoom() {
System.out.println("宝马车---引擎轰鸣之");
}
}
package com.java.dp.builder;
import java.util.ArrayList;
public class BenzBuilder extends CarBuilder{
private Benz benz = new Benz();
@Override
public void setSequence(ArrayList<String> sequence) {
this.benz.setSequence(sequence);
}
@Override
public CarModel getCarModel() {
return benz;
}
}
package com.java.dp.builder;
import java.util.ArrayList;
public class BMWBuilder extends CarBuilder{
private BMW bmw = new BMW();
@Override
public void setSequence(ArrayList<String> sequence) {
this.bmw.setSequence(sequence);
}
@Override
public CarModel getCarModel() {
return bmw;
}
}
package com.java.dp.builder;
import java.util.ArrayList;
public class Director {
private ArrayList<String> sequence = new ArrayList<>();
private BenzBuilder benzBuilder = new BenzBuilder();
private BMWBuilder bmwBuilder = new BMWBuilder();
/**
* 奔驰车A
*/
public Benz getABenz() {
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("stop");
this.benzBuilder.setSequence(sequence);
return (Benz) this.benzBuilder.getCarModel();
}
/**
* 奔驰车B
*/
public Benz getBBenz() {
this.sequence.clear();
this.sequence.add("engineBoom");
this.sequence.add("start");
this.sequence.add("stop");
this.benzBuilder.setSequence(sequence);
return (Benz) this.benzBuilder.getCarModel();
}
/**
* 宝马车
*/
public BMW getBMW() {
this.sequence.clear();
this.sequence.add("engineBoom");
this.sequence.add("start");
this.sequence.add("stop");
this.bmwBuilder.setSequence(sequence);
return (BMW) this.bmwBuilder.getCarModel();
}
}
package com.java.dp.builder;
import java.util.ArrayList;
public class Client {
public static void main(String[] args) {
Director director = new Director();
//奔驰A
System.out.println("奔驰A");
director.getABenz().run();
//奔驰B
System.out.println("奔驰B");
director.getBBenz().run();
//宝马
System.out.println("宝马C");
director.getBMW().run();
}
}
运行结果:
优点:
- 各个具体的建造者相互独立,有利于系统的扩展。
- 客户端不必知道产品内部组成的细节,便于控制细节风险。
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,该模式会增加很多的建造者类。
应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算 法却相对稳定,所以它通常在以下场合使用。
- 相同的方法,不同的执行顺序,产生不同的实践结果
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立 的
结构型
代理(Proxy)模式
代理模式可以理解为代练,一个代练类对核心类进行一些操作。
定义:
为其他对象提供一种代理以控制这个对象的访问。这是一个使用频率非常高的模式。
模式的结构:
- 抽象主题(Subject)角色:抽象主题类可以是接口或抽象类,是一个普通的业务类型定义,声明真实主题和 代理对象实现的业务方法,无特殊要求。
- 真实主题(Real Subject)角色:真实主题角色类也叫作被委托角色、被代理角色,实现了抽象主题中的具体 业务,是代理对象所代表的真实对象,是最终要引用的对象,是业务逻辑的具体执行者。
- 代理(Proxy)角色:也叫做委托类、代理类。他负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后的工作。提供了与真实主题相 同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
模板代码:
//抽象主题类
public interface Subject {
void request();
}
//真实主题类
public class RealSubject implements Subject{
public void request(){
//业务逻辑处理
}
}
//代理类:代理模式的核心就在代理类上
public class Proxy implements Subject{
//要代理哪个实现类
private Subject subject=null;
//通过构造方法传入被代理对象(也可以有其他方式)
public Proxy(Subject subject){
this.subject=subject;
}
public void request(){
preRequest();
resubjectalSubject.request();
postRequest();
}
//预处理
public void preRequest(){
System.out.println("访问真实主题之前的预处理。");
}
//善后工作
public void postRequest(){
System.out.println("访问真实主题之后的善后。");
}
}
//场景类
public class Client {
public static void main(String[] args){
Proxy proxy=new Proxy();
proxy.request();
}
}
实例代码:
package com.java.dp.proxy;
public interface IGamePlayer {
void login(String username,String password);
void killBoss();
void upgrade();
}
package com.java.dp.proxy;
public class GamePlayer implements IGamePlayer{
private String petName = "";
public GamePlayer(String patname){
this.petName = patname;
}
@Override
public void login(String username, String password) {
System.out.println("登录账号:"+username+",昵称:"+petName+"登录成功!");
}
@Override
public void killBoss() {
System.out.println("打怪ing-----");
}
@Override
public void upgrade() {
System.out.println(this.petName+"升级成功!");
}
}
package com.java.dp.proxy;
public class GamePlayerProxy implements IGamePlayer{
private IGamePlayer player = null;
public GamePlayerProxy(IGamePlayer player){
this.player = player;
}
@Override
public void login(String username, String password) {
this.player.login(username,password);
}
@Override
public void killBoss() {
this.player.killBoss();
}
@Override
public void upgrade() {
this.player.upgrade();
}
}
package com.java.dp.proxy;
public class Client {
public static void main(String[] args) {
//定义玩家
IGamePlayer player = new GamePlayer("残影");
//代练者
IGamePlayer proxyPlayer = new GamePlayerProxy(player);
//打游戏
System.out.println("开始时间2020-2-2 13:30");
proxyPlayer.login("cy","123456");
proxyPlayer.upgrade();
System.out.println("结束时间2020-2-2 14:40");
}
}
运行结果图:
最后的结果图不论是proxyPlayer还是player结果显示都是一样的,但是内部确是不一样的。核心思想是代理类和核心类继承了一样的接口,但是代理内部的方法都是调用了核心内部中的方法。
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
适配器(Adapter)模式
定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件 库中的相关组件的内部结构,所以应用相对较少些。
模式的结构
- 目标(Target)角色:该角色定义把其他类转换为何种接口,也就是我们的期望接口。它可以是抽象类或接 口。
- 源(Adaptee)角色:你想把谁转换为目标角色,这个“谁”就是源角色,他是已经存在的、运行良好的类或者 对象,经过适配器角色的包装,他会成为一个新的角色。
- 适配器(Adapter)角色:是适配器模式的核心橘色,其他两个角色都是已经存在的角色,而适配器角色是需 要新建立的,他的职责很简单:把原角色转换为目标角色。如何转换?通过继承或者类关联的方式。
模板代码:
//目标角色
public interface Target{
public void request();
}
//源角色
public class Adaptee{
public void doSomething() {
System.out.println("源角色的----doSomething!");
}
}
//适配器角色
public class Adapter extends Adaptee implements Target{
public void request() {
super.doSomething();
}
}
//场景类:
public class Client{
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
实例代码:
通过手机三孔和两孔的插头,以及转接头的例子来理解适配器模式。
package com.java.dp.adaptor;
public interface ThreePower {
void powerByThree();
}
package com.java.dp.adaptor;
public class TwoPower {
public void powerByTwo(){
System.out.println("两圆孔的插座.");
}
}
package com.java.dp.adaptor;
public class TwoToThreeAdaptor implements ThreePower{
private TwoPower twoPower;
public TwoToThreeAdaptor(TwoPower twoPower){
this.twoPower = twoPower;
}
@Override
public void powerByThree() {
twoPower.powerByTwo();
System.out.println("适配器转换为三孔。");
}
}
package com.java.dp.adaptor;
public class Phone {
private ThreePower threePower;
public Phone(ThreePower threePower){
this.threePower = threePower;
}
public void rechange(){
threePower.powerByThree();
System.out.println("手机用三孔充电.");
}
public void photo(){
System.out.println("手机拍照.");
}
}
package com.java.dp.adaptor;
public class Client {
public static void main(String[] args) {
//只有一个两孔的插座
TwoPower twoPower = new TwoPower();
//通过适配器转换
ThreePower threePower = new TwoToThreeAdaptor(twoPower);
//手机
Phone phone = new Phone(threePower);
phone.rechange();
phone.photo();
}
}
优点:
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点:
- 对类适配器来说,更换适配器的实现过程比较复杂。
适配器模式是一个补偿模式,或者说是一个“补救”模式。通常用来解决接口不相容的问题。一般项目开始的时候用的偏 少。大多数是在项目的需求不断变化的时候,技术为了业务服务的,因此业务在变化的时候,对技术也提出了要求,这些 时候可能就需要这样的补救模式诞生。
装饰(Decorator)模式
定义:
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增 多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的 类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
模式结构:
- 抽象构件(Component)角色:是一个抽象类或者接口,定义最核心的对象,也就是最原始的对象。
- 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:一般是一个抽象类,继承抽象构件,实现其抽象方法,里面不一定有抽象的方 法,在他的属性里一般都会有一个private变量指向Component抽象构件 。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
实例代码:
package com.java.dp.decorator;
public abstract class ScoreReport {
public abstract void show();
public abstract void sign(String name);
}
package com.java.dp.decorator;
public class Decorator extends ScoreReport{
private ScoreReport report;
public Decorator(ScoreReport scoreReport){
this.report = scoreReport;
}
@Override
public void show() {
this.report.show();
}
@Override
public void sign(String name) {
this.report.sign(name);
}
}
package com.java.dp.decorator;
public class MyScoreReport extends ScoreReport{
@Override
public void show() {
System.out.println("尊敬的家长:");
System.out.println("\t以下是你孩子本次考试成绩,请阅读后在后面签名:");
System.out.println("\t语文 60 数学61 自然90");
System.out.println("\t\t\t家长签名:");
}
@Override
public void sign(String name) {
System.out.println("\t\t\t家长签名:"+name);
}
}
package com.java.dp.decorator;
public class SortDecorator extends Decorator{
public SortDecorator(ScoreReport scoreReport) {
super(scoreReport);
}
public void reportSort(){
System.out.println("排名:23");
}
@Override
public void show() {
super.show();
this.reportSort();
}
}
package com.java.dp.decorator;
public class MaxScoreDecorator extends Decorator{
public MaxScoreDecorator(ScoreReport scoreReport) {
super(scoreReport);
}
public void reportMaxScore(){
System.out.println("这次考试最高的成绩:语文78 数学79 自然100");
}
@Override
public void show() {
this.reportMaxScore();
super.show();
}
}
package com.java.dp.decorator;
public class Parent {
public static void main(String[] args) {
//拿到原始成绩单
ScoreReport report = new MyScoreReport();
//加了最高成绩修饰的成绩单
report = new MaxScoreDecorator(report);
report.show();
report.sign("56");
}
}
优点:
- 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合
缺点:
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
应用:
装饰模式在 java语言中的最著名的就是 Java I/O 标准库的设计了。 例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
行为型
策略模式(Strategy)
结果一样,方式不一样。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客 户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不 同的对象对这些算法进行管理。
结构:
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用 这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 策略上下文角色StrategyContext:策略上下文,负责和具体的策略实现交互,通常策略上下文对象会持有一个 真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
实例代码:
package com.java.dp.strategy;
//抽象策略类
public interface TravelStrategy {
void travelType();
}
package com.java.dp.strategy;
public class TrainStrategy implements TravelStrategy{
@Override
public void travelType() {
System.out.println("乘坐火车出行");
}
}
package com.java.dp.strategy;
public class CarStrategy implements TravelStrategy{
@Override
public void travelType() {
System.out.println("开车出行");
}
}
package com.java.dp.strategy;
public class AirStrategy implements TravelStrategy{
@Override
public void travelType() {
System.out.println("乘坐飞机出行");
}
}
package com.java.dp.strategy;
//策略上下文角色
public class Traveler {
//出行策略
private TravelStrategy travelStrategy;
//设置出行策略
public Traveler(TravelStrategy travelStrategy){
this.travelStrategy = travelStrategy;
}
public void travelStyle(){
this.travelStrategy.travelType();
}
}
package com.java.dp.strategy;
import java.util.Random;
public class Clent {
public static void main(String[] args) {
TravelStrategy travelStrategy = null;
for(int i = 0;i<3;i++){
System.out.println("要去杭州玩了,可以由以下的选择方式:");
System.out.println("1飞机");
System.out.println("2火车");
System.out.println("3汽车");
System.out.println("随机选择");
int num = new Random().nextInt(3)+1;
if(num==1){
travelStrategy = new AirStrategy();
}else if (num == 2){
travelStrategy = new TrainStrategy();
}else {
travelStrategy = new CarStrategy();
}
Traveler li = new Traveler(travelStrategy);
li.travelStyle();
}
}
}
优点:
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而 避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点:
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
应用:
- 多个类只有在算法或行为上稍有不同的场景
- 算法需要自由切换的场景
- 需要屏蔽算法规则的场景
观察者(Observer)模式
定义:
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
结构:
- 被观察者(Subject):也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体的被观察者(Concrete Subject):也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 观察者(Observer):它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
实例代码:
package com.java.dp.Observer;
//被观察者
public interface Observable {
//添加观察者
void addObserver(Observer observer);
//删除观察者
void deleteObserver(Observer observer);
//被观察者有所行动,通知观察者
void notifyObserver(String context);
}
package com.java.dp.Observer;
//观察者接口
public interface Observer {
//一旦发现被观察者有动静,自己有所行动
void update(String context);
}
package com.java.dp.Observer;
public interface IStar {
void eat();
void action();
}
package com.java.dp.Observer;
public class Fans implements Observer{
@Override
public void update(String context) {
System.out.println("粉丝:观察到明星活动");
this.action(context);
}
private void action(String context){
System.out.println(context+"-->斯国一!");
}
}
package com.java.dp.Observer;
public class Reporter implements Observer{
@Override
public void update(String context) {
System.out.println("记者:观察到明星活动");
this.action(context);
}
private void action(String context){
System.out.println(context+"-->演技高超之!");
}
}
package com.java.dp.Observer;
import java.util.ArrayList;
public class Wujing implements IStar,Observable{
private ArrayList<Observer> oblist = new ArrayList<>();
@Override
public void eat() {
System.out.println("超级战狼进食ing");
this.notifyObserver("超级战狼进食ing");
}
@Override
public void action() {
System.out.println("超级战狼拍片ing");
this.notifyObserver("超级战狼拍片ing");
}
@Override
public void addObserver(Observer observer) {
this.oblist.add(observer);
}
@Override
public void deleteObserver(Observer observer) {
this.oblist.remove(observer);
}
@Override
public void notifyObserver(String context) {
for (Observer observer:oblist) {
//todo
observer.update(context);
}
}
}
package com.java.dp.Observer;
public class Client {
public static void main(String[] args) {
Observer fan = new Fans();
Observer reporter = new Reporter();
Wujing wu = new Wujing();
wu.addObserver(fan);
wu.addObserver(reporter);
wu.eat();
wu.action();
}
}
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
应用:
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可 以各自独立地改变和复用。
迭代器(Iterator)模式
定义:
提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节表示。 迭代器是为容器服务的,什么是容器?能盛放对象的所有类型都可以认为是容器。例如我们学过的Collection集合 类型等。
结构:
- 抽象容器(Aggregate)角色:定义存储、添加、删除容器对象以及创建迭代器对象的接口。
- 具体容器(ConcreteAggregate)角色:实现抽象容器类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历容器元素的接口,通常包含 hasNext()、first()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对容器对象的遍历,记录遍历的当前位置。
实例代码:
package com.java.dp.iterator;
public interface IProject {
//查看信息
String getProjectInfo();
//添加项目
void add(IProject pro);
//获得一个遍历对象
IprojectIterator iterator();
}
package com.java.dp.iterator;
import java.util.Iterator;
public interface IprojectIterator extends Iterator {
}
package com.java.dp.iterator;
import java.util.ArrayList;
import java.util.List;
public class Project implements IProject{
//存放
private List<IProject> list = new ArrayList<>();
private String name;
private int num;
private int cost;
public Project(){}
public Project(String name,int num,int cost){
this.name = name;
this.num = num;
this.cost = cost;
}
@Override
public String getProjectInfo() {
String info = "项目名称:"+name+"项目人数:"+num+"项目花费:"+cost;
return info;
}
@Override
public void add(IProject pro) {
this.list.add(pro);
}
@Override
public IprojectIterator iterator() {
//todo 返回迭代器对象
return new ProjectIterator(list);
}
}
package com.java.dp.iterator;
import java.util.List;
public class ProjectIterator implements IprojectIterator{
//存放所有项目的集合
private List<IProject> list;
private int cur;
public ProjectIterator(List<IProject> list){
this.list = list;
}
//判断是否还有需要遍历的元素
@Override
public boolean hasNext() {
boolean flag = true;
if(this.cur>=list.size()||this.list.get(this.cur)==null){
flag = false;
}
return flag;
}
//获取下一个元素值
@Override
public Object next() {
return this.list.get(this.cur++);
}
}
package com.java.dp.iterator;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
IProject project = new Project();
project.add(new Project("新功夫",213,123));
project.add(new Project("新开始",2113,1123));
project.add(new Project("新颇说",21113,12113));
IprojectIterator iterator = project.iterator();
while (iterator.hasNext()){
IProject pro = (IProject) iterator.next();
System.out.println(pro.getProjectInfo());
}
}
}
优点:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点:
增加了类的个数,这在一定程度上增加了系统的复杂性。
模板方法(Template Method)模式
定义:
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义 该算法的某些特定步骤。它是应用非常广泛的模式,使用了java的继承机制。
实例代码:
package com.java.dp.TemplateMethod;
public abstract class HummerModel {
public abstract void start();
public abstract void stop();
public abstract void alarm();
public abstract void engineBoom();
public void run(){
this.start();
this.engineBoom();
if(this.isAlarm()){
this.alarm();
}
this.stop();
};
//钩子方法
protected boolean isAlarm(){
return true;
}
}
package com.java.dp.TemplateMethod;
public class HummerModel1 extends HummerModel{
private boolean alarmFlag = true;
@Override
protected boolean isAlarm() {
return alarmFlag;
}
public void isAlarmFlag(boolean alarmFlag) {
this.alarmFlag = alarmFlag;
}
@Override
public void start() {
System.out.println("悍马H1启动");
}
@Override
public void stop() {
System.out.println("悍马H1停止");
}
@Override
public void alarm() {
System.out.println("悍马H1鸣笛");
}
@Override
public void engineBoom() {
System.out.println("悍马H1引擎轰鸣");
}
}
package com.java.dp.TemplateMethod;
public class HummerModel2 extends HummerModel{
@Override
public void start() {
System.out.println("悍马H2启动");
}
@Override
public void stop() {
System.out.println("悍马H2停止");
}
@Override
public void alarm() {
System.out.println("悍马H2鸣笛");
}
@Override
public void engineBoom() {
System.out.println("悍马H2引擎轰鸣");
}
@Override
protected boolean isAlarm() {
return false;
}
}
package com.java.dp.TemplateMethod;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
System.out.println("H1是否需要鸣笛?0--需要 1--不需要");
Scanner input = new Scanner(System.in);
String type = input.nextLine();
HummerModel1 h1 = new HummerModel1();
if("1".equals(type)){
h1.isAlarmFlag(false);
}
h1.run();
HummerModel2 h2 = new HummerModel2();
h2.run();
}
}
优点:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子 类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了 代码阅读的难度。
应用:
1、多个子类有共有的方法,并且基本逻辑相同的时候
2、重要复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能可以由各个子类实现
3、重构的时候,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法约束其行 为。