背景
今天又去看了看设计模式,做个总结
设计模式的七大原则
单一指责原则:一个类只做一件事。
依赖倒转原则:类似于ioc,采用接口编程。
迪米特原则:高内聚,低耦合。
接口隔离原则:应该使用多个接口,而不是用单一的总接口。
开闭原则:对扩展开放、对修改关闭。
合成复用原则:尽量使用对象组合,而不是继承来达到复用目的。
里氏替换原则:子类可以扩展父类的功能,但不能改变原有的功能。
7种结构型模式
代理模式(Proxy)
享元模式采用一个共享来避免大量拥有相同内容对象的开销,数据库的连接池就是享元模式的一个典型应用。
享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)
内蕴状态是存储享元对象内部的,并且不会随环境的改变而有所不同,所以一个享元具有内蕴状态并可以共享
外蕴状态是随环境的改变而改变的、不可共享的。外蕴状态不可以影响内蕴状态,它们是相互独立的
很显然,这里需要产生不同的新对象所以享元模式常常结合工厂模式一块使用,享元工厂通常用来维护享元池的
假设某个咖啡店有几种口味的咖啡,例如拿铁、摩卡、卡布奇诺等,如果这家店要一次性生产几十杯咖啡,那么显然咖啡的口味就可以放进一个共享池中,而不必为每一杯咖啡单独生成
//订单抽象类
public abstract class Order{
public abstract void sell();
}
//订单实现类
public class FlavorOrder extends Order{
public String flavor;
public FlavorOrder(String flavor){
this.flavor = flavor;
}
public void sell(){
System.out.println("生产出一份"+flavor+"口味的咖啡");
}
}
public class FlavorFactory{
//咖啡口味的共享池
private Map<String,Order> flavorPool = new HashMap<String,Order>;
//静态工厂,负责生成订单对象
private static FlavorFactory flavorFactory= new FlavorFactory();
public static FlavorFactory getInstance(){
return flavorFactory;
}
public Order getOrder(String flavor){
Order order = null;
//如果共享池存在该对象,则取出;如不存在,则产生并放入共享池
if(flavorPool.containsKey(flavor)){
order =falvorPool.get(flavor);
}else{
order= new FlavorOrder(flavor);
flavorPool.put(flavor,order);
}
}
public int getTotalFlavorMade(){
return flavorPool.size();
}
}
适配器模式(Adapter)
比如有这样一个变压器,可以转换220V电压和380V电压
现在需要增加一种功能,转换500V的电压,下面用类的适配器模式改造
先定义一个接口,列出了所有需要的转换功能
适配器继承了Transformer类并实现了ITransformer接口
public class Transformer{
public void change220V(){
//输出10V电压
}
public void change380V (){
//输出10V电压
}
}
public interface ITransformer{
public abstract void change220V();
public abstract void change380V();
public abstract void change500V();
}
public classAdapter extends Transformer implements ITransformer{
public void change500V(){
//输出10V电压
}
}
Adapter继承了Transformer类,所以change220V方法与change380V方法都已经存在,只需要实现ITransformer接口中的change500V方法即可
现在Adapter类就可以转换220V、380V、500V三种类型的电压了
因为Adapter继承了Transformer,java是单继承的语言,这个适配器只能为Transformer这一个类服务,所以称之为类的适配器模式
对象的适配器模式
与类的适配器模式不同的是,对象的适配器模式把要改造的目标聚合到适配器类中,同样的变压器问题,对象的适配器模式写法如下:
Transformer类和ITransformer接口与上面完全一样
public class Adapter implements ITransformer{
Transformer transformer ;
//由构造方法传入Transformer对象
public Adapter (Transformer transformer){
this.transformer = transformer;
}
// Transformer类中存在的方法则直接调用
public void change220V(){
transformer.change220V();
}
public void change380V (){
transformer.change220V();
}
// Transformer类中没有的方法则新建
public void change500V(){
//输出10V电压
}
}
桥接模式(bridge)
桥接模式的用意是:将抽象化与实现脱耦,使得二者可以独立的变化
在java中桥接模式最典型的应用是实现JDBC驱动器DriverManager就是连接JDBC应用程序和数据库驱动的"桥"。
考虑这样一个问题,某公司内部的OA系统需要增加这样的一项功能,如果尚未处理完毕的文件,需要发送一条信息进行提示,从业务上看,发送的消息可以分为普通消息、加急消息和特级消息多种;从发送消息的手段上看,又有系统内短消息、电子邮件和手机发送等。
先来看一下不使用设计模式的解决方案
//发送消息的统一接口(普通消息类)
public interface Message{
public void send(String message, String toUser);
}
//系统内短消息的实现类(普通消息类)
public class CommonMessageSMS implements Message{
public void send(String message, StringtoUser){
//以系统内短消息的方式发送
}
}
//邮件发送消息的实现类(普通消息类)
public class CommonMessageEmail implements Message{
public void send(String message, String toUser){
//以系统内短消息的方式发送
}
}
//发送消息的统一接口(加急消息类)
public interface UrgencyMessage extends Message{
public void urge (String toUser); //加急消息类的特有方法,催促用户
}
//系统内短消息的实现类(加急消息类)
public class UrgencyMessageSMSimplements UrgencyMessage {
public void send(String message, String toUser){
//以系统内短消息的方式发送
}
public void urge (String toUser){
//催促用户
}
}
//邮件发送消息的实现类(加急消息类)
public class UrgencyMessageEmailimplements UrgencyMessage {
public void send(String message,String toUser){
//以系统内短消息的方式发送
}
public void urge (String toUser){
//催促用户
}
}
软件在设计阶段必须考虑到以后的功能扩展和修改,上面的代码实现了发送普通消息和加急消息的功能,发送方式分为系统内短消息和电子邮件两种方式。 实际上有两个维度是可变的,即消息的类型和消息的发送方式
假如现在接到甲方的通知,需要增加手机短信的发送方式,需要在每一类消息发送的实现类增加手机短信的发送方式;另外,如果需要增加一种"特急消息"的消息类型,必须实现所有发送方式。
可见,一个维度的变化会引起另一个维度相应的变化,从而使程序扩展起来非常的困难,根本原因是因为消息的抽象和实现是混杂在一起的,要想解决这个问题,必须把这两个维度分开,也就是将抽象部分和实现部分分开,让它们相互的独立,这样就可以独立的变化,使扩展变得简单
按照桥接模式的结构,给抽象部分和实现部分分别定义接口,然后分别实现它们就可以了
//实现部分定义的接口,发送信息的统一接口
public interface MessageImplementor {
public void send(String message,String toUser);
}
//抽象部分定义的接口
public abstract class AbstractMessage{
protected MessageImplementor impl; //持有一个实现部分的对象
public AbstractMessage(MessageImplementor impl){ //构造方法
this.impl = impl;
}
public sendMessage(String message,String toUser){
this.impl.send(message, toUser); //调用impl的方法
}
}
//以站内短消息的形式发送信息
public class MessageSMS implements MessageImplementor{
public void send(String message,String toUser){
//执行发送消息的代码
}
}
//以Email的形式发送信息
public class MessageEmail implements MessageImplementor{
public void send(String message,String toUser){
//执行发送消息的代码
}
}
下面来扩展抽象的消息接口
//普通消息的实现
public class CommonMessage extends AbstractMessage{
//构造方法,传入MessageImplementor的一个实现类
public CommonMessage(MessageImplementor impl){
super(impl);
}
//直接调用父类的方法,其他什么都不做
public void sendMessage(String message,String toUser){
super.sendMessage(message,toUser);
}
}
//加急消息
public class UrgencyMessage extends AbstractMessage{
//构造方法,传入MessageImplementor的一个实现类
public CommonMessage(MessageImplementor impl){
super(impl);
}
public void sendMessage(String message,String toUser){
urge(toUser){
//加急消息特有的处理方法
}
//然后在调用父类的方法发送消息
super.sendMessage(message,toUser);
}
}
到此,原始代码已经写完,现在系统可以发送普通消息、加急消息,发送方式可以是站内短消息、Email。现在重新考虑扩展的问题,同样的问题,增加一种特急消息和一种手机短信的发送方式
//特急消息
public class SpecialUrgencyMessage extends AbstractMessage{
//构造方法,传入MessageImplementor的一个实现类
public CommonMessage(MessageImplementor impl){
super(impl);
}
public void sendMessage(String message,String toUser){
urge(toUser){
//特急消息特有的处理方法
}
//然后在调用父类的方法发送消息
super.sendMessage(message,toUser);
}
}
//以手机短信的形式发送信息
public class MessageMobile implements MessageImplementor{
public void send(String message,String toUser){
//执行发送消息的代码
}
}
可见正增加了两个类,就实现了要扩展的功能,如果按照上面没有使用模式的代码来扩展,需要增加6个类
测试类
//创建一个站内短消息的发送方式
MessageImplementor impl = new MessageSMS();
//创建一个普通消息对象,并传入发送方式
AbstractMessage m = new CommonMessage(impl);
m.sendMessage("请你喝茶","小陈");
//创建一个加急消息对象,并传入发送方式
AbstractMessage m = new UrgencyMessage(impl);
m.sendMessage("请你喝茶","小陈");
//把实现方式切换成手机短信
MessageImplementor impl2 = new MessageMobile();
/创建一个特急消息对象,并传入发送方式
AbstractMessage m = new SpecialUrgencyMessage(impl2);
m.sendMessage("请你喝茶","小陈");
组合模式(composite)
组合模式所代表的数据构造是一群具有统一接口界面的对象集合
组合模式在处理树形结构的问题时比较方便,文件/目录结构是一个典型的应用
//File与Folder的共同接口
public interface Root{
public boolean addFile(File file);
public boolean removeFile(File file);
public List<Root> getFile();
public void display();
}
//文件实现类
public class Fileimplements Root{
String name;
public File(String name){
this.name = name;
}
public List<Root> getFile(){
return null;
}
public boolean addFile(File file){
return false;
}
public boolean removeFile(File file){
return false;
}
public void display(){
System.out.println("文件名字:"+name);
}
}
//目录实现类
public class Folder implements Root{
String name;
List<Root> folder;
public Folder(String name){
this.name = name;
this.folder = new ArrayList<Root>;
}
public List<Root> getFile(){
return folder;
}
public boolean addFile(File file){
return folder.add(file);
}
public boolean removeFile(File file){
return folder.remove(file);
}
public void display(){
for(Root f : folder){
if(f instanceof Folder){
System.out.println("目录");
}
f.display();
}
}
}
测试类
Root root1 = new Folder("c://");
Root root2 = new Folder("d://");
Root win = new Folder("windows");
Root sys = new Folder("system");
Root hw = new File("HellowWorld.java");
root1.addFile(win);
root1.addFile(sys);
win.addFile(hw);
root1.display();
root2.display();
装饰模式(Decorator/Wapper)
装饰模式能够在不必改变原类文件和使用继承的情况下,动态扩展一个对象的功能,装饰模式是通过创建一个包装对象来实现的,也就是用装饰来包裹真实的对象
java中的I/O流就是典型的装饰模式的应用
//定义咖啡的接口
public interface ICoffee {
void showCoffee();
double showPrice();
}
//咖啡对象的实现类,这是一个待装饰的对象,可以对咖啡进行加糖、加牛奶等相关的操作
public classCoffee implements ICoffee{
private String name;
private double price;
public Coffee(String name,double price){
this.setName(name);
this.setPrice(price);
}
public void showCoffee(){
System.out.println(this.getName()+"咖啡");
}
public double showPrice(){
return this.getPrice();
}
//省略相应的setter、getter方法
}
//咖啡的装饰器,同样实现ICoffee接口,需要保证装饰器和被装饰的对象拥有一致的接口
public class SugarDecorator implements ICoffee{
privateICoffee coffee; //持有一个接口的实现对象
public void setCoffee(ICoffee cf){
coffee = cf;
}
public void showCoffee(){
System.out.println("加糖的"); //加上装饰
coffee.showCoffee(); //调用传入对象的相应方法
}
public double showPrice(){
return 10.0 + coffee.showPrice();
}
}
//咖啡的另一种装饰器
public class MilkDecorator implements ICoffee{
privateICoffee coffee; //持有一个接口的实现对象
public void setCoffee(ICoffee cf){
coffee = cf;
}
public void showCoffee(){
System.out.println("加牛奶的"); //加上装饰
coffee.showCoffee(); //调用传入对象的相应方法
}
public double showPrice(){
return 15.0 + coffee.showPrice();
}
}
//测试类
Coffee coffee =new Coffee("拿铁",55.0);
SugarDecoratorsugar = new SugarDecorator();
MilkDecoratormilk = new MilkDecorator();
sugar.setCoffee(coffee); //给咖啡加糖
milk. setCoffee(sugar); //再给咖啡加牛奶
milk.showCoffee();
System.out.println(milk.showPrice());
打印出来的是:
加牛奶的加糖的拿铁咖啡
80.0
外观模式/门面模式(Facade)
外观模式是一个能为子系统和客户提供简单接口的类,当正确使用外观时,客户不再与子系统中的类交互,而是与外观交互,外观承担了与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统与客户的耦合度
外观模式是有代理模式发展而来的,与代理模式类似,代理模式是一对一的代理,而外观模式是一对多的代理。与装饰模式不同的是,装饰模式为对象增加功能,而外观模式则是提供一个简化的调用方式。
用户类只需要调用Computer类来操纵计算机CPU、内存、硬盘的关闭与开启,而不需要与各个子系统类直接交互
享元模式
public class CPU{
public void startup(){
System.out.println("启动CPU");
}
public void shutdown(){
System.out.println("关闭CPU");
}
}
public class Memory{
public void startup(){
System.out.println("加载内存");
}
public void shutdown(){
System.out.println("清空内存");
}
}
public class Disk{
public void startup(){
System.out.println("加载硬盘");
}
public void shutdown(){
System.out.println("卸载硬盘");
}
}
public class Computer{
private CPU cpu;
private Memorymemory;
private Diskdisk;
public Computer(){
this.cpu = new CPU();
this.memory = new Memory();
this.disk = new Disk();
}
public void stratup(){
System.out.println("启动计算机");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("启动计算机完成");
}
public void shutdown(){
System.out.println("关闭计算机");
cpu.shutdown();
memory.shutdown();
disk. shutdown ();
System.out.println("关闭计算机完成");
}
}
享元模式(Flyweight)
享元模式采用一个共享来避免大量拥有相同内容对象的开销,数据库的连接池就是享元模式的一个典型应用。
享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)
内蕴状态是存储享元对象内部的,并且不会随环境的改变而有所不同,所以一个享元具有内蕴状态并可以共享
外蕴状态是随环境的改变而改变的、不可共享的。外蕴状态不可以影响内蕴状态,它们是相互独立的
很显然,这里需要产生不同的新对象所以享元模式常常结合工厂模式一块使用,享元工厂通常用来维护享元池的
假设某个咖啡店有几种口味的咖啡,例如拿铁、摩卡、卡布奇诺等,如果这家店要一次性生产几十杯咖啡,那么显然咖啡的口味就可以放进一个共享池中,而不必为每一杯咖啡单独生成
//订单抽象类
public abstract class Order{
public abstract void sell();
}
//订单实现类
public class FlavorOrder extends Order{
public String flavor;
public FlavorOrder(String flavor){
this.flavor = flavor;
}
public void sell(){
System.out.println("生产出一份"+flavor+"口味的咖啡");
}
}
public class FlavorFactory{
//咖啡口味的共享池
private Map<String,Order> flavorPool = new HashMap<String,Order>;
//静态工厂,负责生成订单对象
private static FlavorFactory flavorFactory= new FlavorFactory();
public static FlavorFactory getInstance(){
return flavorFactory;
}
public Order getOrder(String flavor){
Order order = null;
//如果共享池存在该对象,则取出;如不存在,则产生并放入共享池
if(flavorPool.containsKey(flavor)){
order =falvorPool.get(flavor);
}else{
order= new FlavorOrder(flavor);
flavorPool.put(flavor,order);
}
}
public int getTotalFlavorMade(){
return flavorPool.size();
}
}