什么是设计模式
设计模式(Design pattern):代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的
作用、目的:设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因
设计模式的类型
根据设计模式的参考书 《《Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素)》》 中所提到的,总共有 23 种设计模式。这些模式大致可以分为三大类:
- 创建型模式(Creational Patterns):对象实例化的模式,创建型模式用于解耦对象的实例化过程。
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活- 工厂模式(Factory Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 单例模式(Singleton Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
- 结构型模式(Structural Patterns):把类或对象结合在一起形成一个更大的结构
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式- 适配器模式(Adapter Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter、Criteria Pattern)
- 组合模式(Composite Pattern)
- 装饰器模式(Decorator Pattern)
- 外观模式(Facade Pattern)
- 享元模式(Flyweight Pattern)
- 代理模式(Proxy Pattern)
- 行为型模式(Behavioral Patterns):类和对象如何交互,及划分责任和算法
这些设计模式特别关注对象之间的通信- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 备忘录模式(Memento Pattern)
- 观察者模式(Observer Pattern)
- 状态模式(State Pattern)
- 空对象模式(Null Object Pattern)
- 策略模式(Strategy Pattern)
- 模板模式(Template Pattern)
- 访问者模式(Visitor Pattern)
来张图、简单直观:
设计模式的六大原则
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承。
设计模式示例
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
示例:
//单例模式、比如:工程类实例化采用此模式
public class TestSingleton {
懒汉式单例、只有当第一次调用 getlnstance 时才创建这个单例
若编写的是多线程程序,则不要删除代码中的关键字 volatile 和 synchronized,否则存在线程安全问题
若不删除这两关键字能保证线程安全,但是每次访问时都要同步,会影响性能,消耗更多资源
//private static volatile TestSingleton instance = null; //保证 instance 在所有线程中同步
//
//private TestSingleton() { //禁止类在外部被实例化 }
//
//public static synchronized TestSingleton getInstance() { //getInstance 方法前加同步
// if (instance == null) instance = new TestSingleton();
// return instance;
//}
//饿汉式单例、 类一旦加载就创建一个单例,保证在调用 getInstance 之前单例就存在了
//它在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,是线程安全的,能直接用于多线程
private static final TestSingleton instance = new TestSingleton();
private TestSingleton(){}
public static TestSingleton getInstance() {
return instance;
}
}
工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。工厂模式有3种不同的实现方式(简单工厂模式、工厂方法模式、抽象工厂模式)
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品
优点:
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事
示例:
//工厂模式有3种不同的实现方式:简单工厂模式、工厂方法模式、抽象工厂模式
public class TestFactory {
简单工厂模式、又叫静态工厂方法模式
只要一个工厂类就可以完成所有目标对象,这种模式叫“简单工厂模式”
//public static TestProduct simpleProduct(int kind) {
// return new TestProduct();
//}
//
//private static class TestProduct {
// public void show() {
// System.out.println("具体产品显示...");
// }
//}
工厂方法模式、由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成
//public static void main(String[] args) {
// try {
// TestProduct a;
// a = getObject(SubProduct1.class);
// System.out.println(a);
// a.show();
// a = getObject(SubProduct2.class);
// System.out.println(a);
// a.show();
// } catch (Exception e) {
// System.out.println(e.getMessage());
// }
//}
//public static <T> T getObject(Class<T> tClass) throws Exception{
// //Class alz = Class.forName(className);
// return tClass.newInstance();
//}
//
抽象产品:提供了产品的接口
//interface TestProduct {
// public void show();
//}
//static class SubProduct1 implements TestProduct {
// public void show() {
// System.out.println("产品1显示...");
// }
//}
//static class SubProduct2 implements TestProduct {
// public void show() {
// System.out.println("产品2显示...");
// }
//}
public static void main(String[] args) {
//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
//获取形状为 Rectangle 的对象
Shape shape2 = shapeFactory.getShape("RECTANGLE");
//调用 Rectangle 的 draw 方法
shape2.draw();
//获取形状为 Square 的对象
Shape shape3 = shapeFactory.getShape("SQUARE");
//调用 Square 的 draw 方法
shape3.draw();
}
//抽象工厂模式(Abstract Factory Pattern):围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
//这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
//在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象
private interface Shape {
void draw();
}
static class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
static class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
//abstract class AbstractFactory {
interface AbstractFactory {
public abstract Shape getShape(String shape) ;
//public abstract Color getColor(String color);
}
static class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
} else if(choice.equalsIgnoreCase("COLOR")){
//return new ColorFactory();
}
return null;
}
}
static class ShapeFactory implements AbstractFactory {
@Override
public Shape getShape(String shapeType){
if(shapeType == null) return null;
if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
}
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用
主要解决:在运行期建立和删除原型。
何时使用:
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点:
1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2、必须实现 Cloneable 接口
示例:
//原型模式的克隆分为浅克隆和深克隆。
// 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
// 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
//Java中的Object类提供了浅克隆的clone()方法,原型类只要实现Cloneable接口就可实现对象的浅克隆,
public class TestRealizetype implements Cloneable {
TestRealizetype() {
System.out.println("具体原型创建成功!");
}
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (TestRealizetype) super.clone();
}
//原型模式通常适用于以下场景:
// 对象之间相同或相似,即只是个别的几个属性不同的时候。
// 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
// 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
// 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
public static void main(String[] args) throws CloneNotSupportedException {
TestRealizetype obj1 = new TestRealizetype(); //具体原型类
TestRealizetype obj2 = (TestRealizetype) obj1.clone();
System.out.println("obj1==obj2?" + (obj1 == obj2));
}
}
建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象,一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类
示例:
//建造者模式、使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
public class TestBuilds {
//一些基本部件不会变,其组合经常变化时、使用场景:需要生成的对象具有复杂的内部结构、需要生成的对象内部属性本身相互依赖。
interface Items {
public String name();
public Packing packing();
public float price();
}
interface Packing {
public String packed();
}
static class Wrapper implements Packing {
@Override
public String packed() {
return "Wrapper Packing";
}
}
//abstract class Burger implements Items {
interface Burger extends Items {
@Override
public default Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
//static class VegBurger extends Burger {
static class VegBurger implements Burger {
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "Veg Burger";
}
}
static class ChickenBurger implements Burger {
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "Chicken Burger";
}
}
static class MealBuilder {
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
return meal;
}
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
return meal;
}
}
static class Meal {
private List<Items> item = new ArrayList<Items>();
public void addItem(Items tms){
item.add(tms);
}
public float getCost(){
float cost = 0.0f;
for (Items item : item) {
cost += item.price();
}
return cost;
}
public void showItems(){
for (Items item : item) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().packed());
System.out.println(", Price : "+item.price());
}
}
}
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}
适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用:
- 系统需要使用现有的类,而此类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
- 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖
优点:
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点:
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
示例:
//适配器模式(Adapter Pattern):作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能
//例:读卡器是内存卡和笔记本之间的适配器。将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡
public class TestAdapterModel {
//主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的
//使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
//注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题
interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
static class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) { }
}
static class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) { }
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
interface MediaPlayer {
public void play(String audioType, String fileName);
}
static class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
static class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音乐文件的内置支持
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file. Name: " + fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
else if (audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. "+ audioType + " format not supported");
}
}
}
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂
示例:
//装饰器模式(Decorator Pattern):向一个现有对象添加新功能,同时不改变其结构。这种类型设计模式属于结构型模式,它是作为现有的类的一个包装
public class TestDecorators {
//动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活
//使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销 可代替继承
interface Shape {
void draw();
}
static class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
static class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
//abstract class ShapeDecorator implements Shape {
static class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
static class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用,它向现有的系统添加一个接口,来隐藏系统的复杂性。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
如何解决:客户端不与系统耦合,外观类与系统耦合
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
缺点:不符合开闭原则,如果要改东西很麻烦,继承重写都不合适
示例:
//外观模式(Facade Pattern):隐藏系统的复杂性并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,
public class TestFacades {
//使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
//注意事项:在层次化结构中,可以使用外观模式定义系统中每一层的入口
interface Shape {
void draw();
}
static class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
static class Square implements Shape {
@Override
public void draw() {
System.out.println("Square::draw()");
}
}
static class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
static class ShapeMaker { //创建一个外观类
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
}
代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,我们创建具有现有对象的对象,以便向外界提供功能接口。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂
示例:
//代理模式(Proxy Pattern):一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
//在代理模式中,我们创建具有现有对象的对象,为其他对象提供一种代理以控制对这个对象的访问。
public class TestAgentModel {
//按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。
// 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理
// 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
// 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制
interface Image {
void display();
}
static class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
static class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
示例:
//当对象间存在一对多关系时,则使用观察者模式(Observer Pattern):当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式
public class TestObservers {
//定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
static class Subject {
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
abstract static class Observer {
protected Subject subject;
public abstract void update();
}
static class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "+ Integer.toBinaryString( subject.getState() ) );
}
}
static class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "+ Integer.toOctalString( subject.getState() ) );
}
}
static class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: "+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
public static void main(String[] args) {
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露
示例:
//策略模式(Strategy Pattern):一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
//策略模式:创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变context对象的执行算法
public class TestStrategys {
//定义一系列算法,把它们一个个封装起来并使它们可相互替换、若一个系统的策略多于四个,就需考虑使用混合模式解决策略类膨胀问题
//使用场景:1、如一个系统里面许多类它们之间的区别仅在于它们的行为,那么用策略模式可以动态的让一个对象在许多行为中选择一种行为
//2、一个系统需要动态地在几种算法中选择一种。 3、若一个对象有很多的行为,如不用恰当的模式,这些行为只好用多重的条件选择语句来实现
interface Strategy {
public int doOperation(int num1, int num2);
}
static class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
static class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
static class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
static class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
OK,简单记录下几种设计模式,实际开发中,具体怎么运用就仁者见仁了!