-
设计模式六大原则
-
单一职责原则(Single Responsibility Principle, SRP)
-
每个类应该仅有一个引起它变化的原因。
-
这意味着一个类只应该专注完成一项任务或功能。
-
举例
-
考虑一个
User
类,用于表示用户信息,例如用户名和密码。如果我们遵循单一职责原则,这个类应该只负责用户的信息表示,而不涉及与用户认证相关的逻辑。 -
// 不遵循单一职责原则的例子 public class User { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } // 不应该包含与用户认证相关的逻辑 public boolean authenticateUser(String enteredPassword) { return this.password.equals(enteredPassword); } }
-
述例子中,
User
类不仅表示用户的信息,还包含了用户认证的逻辑。这违反了单一职责原则。更好的做法是将用户认证的逻辑移到一个独立的类中
-
-
开闭原则**(Open/Closed Principle,** OCP**)**
-
软件实体应当对扩展开放,对修改关闭。
-
这意味着设计时应易于扩展,而无需修改现有的代码。
-
举例
-
例如,通过定义接口或抽象类,你可以在不修改现有代码的情况下创建新的实现,并通过接口的引用来使用不同的实现。
-
// 实现矩形 public class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing Rectangle"); } } // 图形绘制系统 public class DrawingSystem { // 绘制图形的方法,不依赖于具体的图形类型 public void drawShape(Shape shape) { shape.draw(); } }
-
-
里氏替换原则**(Liskov Substitution Principle, LSP)**
-
子类型必须能够替换掉它们的基类型。
-
这意味着派生类在不改变原有行为的前提下,应能替换其基类对象。
-
举例
-
类的对象应当能够替换掉它们的父类对象,且不改变程序的正确性。
-
// 符合里氏替换原则的例子 // 父类 public class Shape { protected int width; protected int height; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int calculateArea() { return width * height; } } // 子类-长方形 public class Rectangle extends Shape { // 可以保留父类的行为,也可以有自己的特定行为 } // 另一个子类-正方形 public class Square extends Shape { @Override public void setWidth(int width) { super.setWidth(width); super.setHeight(width); // 正方形的宽高一致 } @Override public void setHeight(int height) { super.setWidth(height); // 正方形的宽高一致 super.setHeight(height); } } // 在使用父类的地方可以使用其任何子类 public class ExampleUsage { public void processShape(Shape shape) { int area = shape.calculateArea(); // 处理其他逻辑 } }
-
-
接口隔离原则**(Interface Segregation Principle,** ISP**)**
- 不应该强迫客户依赖于它们不用的方法。
- 这个原则推荐创建小且专用的接口,而不是大而全的单一接口。
-
依赖倒置原则**(Dependency Inversion Principle, DIP)**
-
高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
-
抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
-
这个原则鼓励使用接口和抽象类来建立松耦合的设计。
-
详细解释
-
依赖倒置原则的核心思想是面向接口编程而不是面向实现编程。在传统的软件开发中,高层模块(如业务逻辑层)通常直接依赖低层模块(如数据访问层、工具类等),这样的设计导致了高层模块和低层模块之间的强耦合,不利于代码的扩展和维护。
-
例子
-
假设我们有一个电子商务系统,我们需要实现一个订单处理模块。按照依赖倒置原则,我们首先定义一个订单处理的抽象接口
IOrderProcessor
。然后,我们可以创建具体的实现类OnlineOrderProcessor
用于处理在线订单,POSOrderProcessor
用于处理线下店铺的订单等等。高层的业务逻辑模块只需要依赖于IOrderProcessor
接口而不是具体的实现类,这样当我们需要改变订单处理方式时,只需要提供一个新的实现类,不需要修改高层模块的代码。 -
// 抽象 public interface DataService { String getData(); } // 低层模块(数据服务)实现了抽象 public class DatabaseService implements DataService { @Override public String getData() { // 实际的数据获取逻辑 return "Data from database"; } } // 高层模块(业务逻辑)依赖于抽象 public class BusinessLogic { private DataService dataService; // 通过构造函数注入依赖 public BusinessLogic(DataService dataService) { this.dataService = dataService; } public void doSomething() { // 使用抽象而不是直接依赖于具体实现 String data = dataService.getData(); // 处理业务逻辑 } }
-
-
迪米特法则**(Law of Demeter,** LoD**)也称作最少知识原则**
-
一个对象应当对其他对象有最少的了解。
-
这不是指一个对象只能知道有限的其他对象,而是指在设计时应当尽量减少各个对象之间的交互。
-
在实际编程中,迪米特法则鼓励我们减少对象之间的交互,如果一个类需要获取另一个类的某种信息,不应直接去调用对方的方法,而是可以通过自己的方法来获取。这样做的目的是减少类之间的耦合度,增强模块的独立性,使得代码更容易维护和扩展。
-
考虑一个购物车系统,其中包含顾客、购物车和商品三个类。
-
根据迪米特原则,顾客类应该尽可能不直接获取商品类的信息,而是通过购物车类来处理商品的添加和删除等操作。
-
这样可以降低顾客类对商品类的依赖,提高系统的灵活性和可维护性。
-
// 商品类
public class Product {
private String name;
public Product(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 购物车类
public class ShoppingCart {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
public void removeProduct(Product product) {
products.remove(product);
}
}
// 顾客类
public class Customer {
private ShoppingCart shoppingCart;
public Customer(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
public void addToCart(Product product) {
shoppingCart.addProduct(product);
}
public void removeFromCart(Product product) {
shoppingCart.removeProduct(product);
}
}
2.设计模式分类
GoF提出的设计模式总共有23种,根据目的准则分类,分为三大类。
- 创建型设计模式,共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型设计模式,共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型设计模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
另外,随着设计模式的发展也涌现出很多新的设计模式:它们分别是规格模式、对象池模式、雇工模式、黑板模式和空对象模式等。
创建型设计模式
单例模式
定义:保证只有一个类一个实例,并提供一个访问它的全局访问节点
客户端通过Singleton.getInstance()方法来获取单例类Singleton的实例
单例类的六种写法
(一)饿汉式(线程安全)
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
说明: 先不管需不需要使用这个实例,直接先实例化好实例 (饿死鬼一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了。
优点: 提前实例化好了一个实例,避免了线程不安全问题的出现。
缺点: 直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会操作系统的资源浪费。
(二)懒汉式(线程不安全)
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
说明: 先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。
优点: 延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。
缺点: 线程不安全,多线程环境下,如果多个线程同时进入了 if (uniqueInstance == null) ,若此时还未实例化,也就是uniqueInstance == null,那么就会有多个线程执行 uniqueInstance = new Singleton(); ,就会实例化多个实例;
(三)懒汉式(线程安全)
public class Singleton {
private static Singleton uniqueInstance;
private static singleton() {
}
private static synchronized Singleton getUinqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
说明: 实现和 线程不安全的懒汉式 几乎一样,唯一不同的点是,在get方法上 加了一把 锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。
优点: 延迟实例化,节约了资源,并且是线程安全的。
缺点: 虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方***使线程阻塞,等待时间过长。
(四)双重检查锁实现(线程安全)
实现:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
明: 双重检查数相当于是改进了 线程安全的懒汉式。线程安全的懒汉式其缺点是性能降低,造成的原因是因为即使实例已经实例化,依然每次都会有锁。
而现在,我们将锁的位置变了,并且多加了一个检查。 也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。
(五)静态内部类实现(线程安全)
实现:
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
说明: 首先,当外部类 Singleton 被加载时,静态内部类 SingletonHolder 并没有被加载进内存。当调用 getUniqueInstance() 方法时,会运行 return SingletonHolder.INSTANCE; ,触发了 SingletonHolder.INSTANCE ,此时静态内部类 SingletonHolder 才会被加载进内存,并且初始化 INSTANCE 实例,而且 JVM 会确保 INSTANCE 只被实例化一次。
优点: 延迟实例化,节约了资源;且线程安全;性能也提高了。
(六)枚举类实现(线程安全)
实现:
public enum Singleton {
INSTANCE; // 定义一个枚举的元素,它就代表了Singleton的一个实例。
// 单例可以有自己的操作
public void doSomething() {
// 这里可以进行你希望的任何操作
System.out.println("Do something");
}
}
public class SingletonDemo {
public static void main(String[] args) {
// 通过Singleton.INSTANCE来访问实例
Singleton.INSTANCE.doSomething();
}
}
枚举单例模式的优点:
- 简单易懂:代码易于理解,实现单例的方式非常直接。
- 线程安全:枚举实例的创建是由JVM保证线程安全的,避免了多线程下的同步问题。
- 自动支持序列化机制:枚举实现的单例模式可以避免序列化破坏单例的问题。
- 防止反射攻击:由于枚举类没有构造方法(默认私有的),故不能通过反射来创建枚举类的新实例。
- 实现简单:无需手动编写防止序列化和防止反射攻击的代码。
枚举单例模式的缺点:
- 不够灵活:枚举的单例模式不允许懒加载,即枚举单例会在枚举类被加载时就实例化,这在某些场景下可能不太适用。
简单工厂模式
简单工厂模式(又叫作静态工厂方法模式),其属于创建型设计模式,但是并不属于23种GoF设计模式之一。提到它是为了让大家能够更好地理解后面讲到的工厂方法模式。
定义:简单工厂模式属于创建型设计模式,其又被称为静态工厂方法模式,这是由一个工厂对象决定创建出哪一种产品类的实例。
- Factory:工厂类,这是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- Product:抽象产品类,这是简单工厂模式所创建的所有对象的****父类,它负责描述所有实例所共有的公共接口。
- Product:具体产品类,这是简单工厂模式的创建目标。
简单工厂模式的简单实现
这里我们用生产计算机来举例,假设有一个计算机的代工生产商,它目前已经可以代工生产联想计算机了。随着业务的拓展,这个代工生产商还要生产惠普和华硕的计算机。这样我们就需要用一个单独的类来专门生产计算机,这就用到了简单工厂模式。下面我们来实现简单工厂模式。
1,抽象产品类
我们创建一个计算机的抽象产品类,其有一个抽象方法用于启动计算机,如下所示:
public abstract class Computer {
// 产品的抽象方法,由具体产品类实现
public abstract void start();
}
2,具体产品类
接着我们创建各个品牌的计算机,其都继承了自己的父类Computer,并实现了父类的start方法。具体的计算机产品分别是联想计算机、惠普计算机和华硕计算机:
public class LenovoComputer extends Computer{
@Override
public void start(){
System.out.println("联想计算机启动");
}
}
public class HpComputer extends Computer{
@Override
public void start() {
System.out.println("惠普计算机启动");
}
}
public class AsusComputer extends Computer {
@Override
public void start(){
System.out.println("华硕计算机启动");
}
}
3,工厂类
接下来创建一个工厂类,它提供了一个静态方法createComputer来生产计算机。你只需要传入自己想生产的计算机的品牌,它就会实例化相应品牌的计算机对象,代码如下所示:
public class ComputerFactory {
public static Computer create(String type) {
Computer mComputer = null;
switch (type) {
case "lenovo":
mComputer = new LenovoComputer();
break;
case "hp":
mComputer = new HpComputer();
break;
case "asus":
mComputer = new AsusComputer();
break;
}
return mComputer;
}
}
4,客户端调用
客户端调用工厂类,传入"hp"生产出惠普计算机并调用该计算机对象的start方法,如下所示:
public class CreatComputer {
public static void main(String[]args){
ComputerFactory.create("hp").start();
}
}
使用简单工厂模式的场景和优缺点
- 使用场景:
- 工厂类负责创建的对象比较少。
- 客户只需知道传入工厂类的参数,而无须关心创建对象的逻辑。
- 优点:使用户根据参数获得对应的类实例,避免了直接实例化类,降低了耦合性。
- 缺点:可实例化的类型在编译期间已经被确定。如果增加新类型,则需要修改工厂,这违背了开放封闭原则。使用简单工厂模式时需要知道所有要生成的类型,当子类过多或者子类层次过多时不适合使用简单工厂模式。
工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
在工厂方法模式中有如下角色。
- Product:抽象产品类。
- ConcreteProduct:具体产品类,实现Product接口。
- Factory:抽象工厂类,该方法返回一个Product类型的对象。
- ConcreteFactory:具体工厂类,返回ConcreteProduct实例。
工厂方法的简单实现
1,创建抽象工厂
public abstract class ComputerFactory {
public abstract <T extends Computer> T createComputer(Class<T> clazz);
}
2,创建具体工厂
继承自抽象工厂,通过反射生成不同厂家的计算器:
package com.example.http;
public class GDComputerFactor extends ComputerFactory {
@Override
public <T extends Computer> T createComputer(Class<T> clazz) {
Computer computer = null;
String computerName = clazz.getName();
try {
computer = (Computer) Class.forName(computerName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) computer;
}
}
3,客户端调用
public class Client {
public static void main(String[]args){
ComputerFactory computerFactory =new GDComputerFactor();
LenovoComputer mLenovoComputer=computerFactory.createComputer(LenovoCo puter.class);
mLenovoComputer.start();
HpComputer mHpComputer=computerFactory.createComputer(HpComputer.class);
mHpComputer.start();
AsusComputer mAsusCoputerr=coputerFactory.createComputer(AsusComputer.class);
mAsusComputerr.start();
}
}
建造者模式
建造者模式,也叫生成器模式,他是创建一个复杂对象的创建型设计模式,将构建复杂对象的过程和他的部件解耦,使得构建过程和部件的表示分离开来
定义:将一个复杂的对象构建与他的表示分离,使得同样的构建过程可以创建不同的表示
- Director:导演类,负责安排已有模块的顺序,然后通知Builder开始建造。
- Builder:抽象类,规范产品的组建,一般由子类实现。
- ConcreteBuilder:具体建造者,实现抽象Builder类定义的所有方法,并且返回一个组建好的对象。
- Product:产品类。
建造者模式的简单实现
1,创建产品类
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 showProduct() {
System.out.println("Product features: PartA = " + partA + ", PartB = " + partB + ", PartC = " + partC);
}
}
2,抽象建造者和具体建造者
// 抽象建造者
public abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getResult() {
return product;
}
}
// 具体建造者
public class ConcreteBuilder extends Builder {
public void buildPartA() {
product.setPartA("Build Part A");
}
public void buildPartB() {
product.setPartB("Build Part B");
}
public void buildPartC() {
product.setPartC("Build Part C");
}
}
3,导演类统一组装
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
// 构建最终产品
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
4,客户端调用
// 客户端
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
product.showProduct();
}
}
Product
类表示复杂对象,具有多个部件。ConcreteBuilder
是Builder
的实现类,提供构建产品各个组成部分的具体实现。Director
类负责管理构建步骤,Client
类中创建了具体的建造者,并通过指挥者来构建产品。在整个过程中,客户端与实际建造细节解耦,只关心最终产品。
四种模式总结
- 单例模式:核心在于一个类创建一个实例,这里除了饿汉式(线程不安全),其他五种方式全部是线程安全的
- 简单工厂模式:这里模式核心在于创建了一个工厂,这个工厂可以根据参数不同返回不同类的实例,这些实例具有同一个父类
- 工厂方法模式:核心在于定义一个创建对象的接口,但让实现该接口的类来决定实例化哪一个类
- 建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。主要用于需要生成的对象具有复杂的内部结构,这些对象的生成过程需要独立于构件的创建过程和组合方式
工厂方法模式和简单工厂模式的主要区别体现在简单工厂模式更简单,但不够灵活(里面的switch)。如果需要更大的灵活性使用工厂方法模式
结构型设计模式
结构型设计模式将从程序的结构上解决模块之间的耦合问题,它包括适配器模式、代理模式、装饰模式、外观模式、桥接模式、组合模式和享元模式。本节会介绍代理模式、装饰模式、外观模式和享元模式。
定义:为其他对象提供一种代理以控制对这个对象的访问
- Subject:抽象主题类,声明真实主题与代理的共同接口方法。
- RealSubject:真实主题类,代理类所代表的真实主题。客户端可以通过代理类间接地调用真实主题类的方法。
- Proxy:代理类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法。
- Client:客户端类。
代理模式的简单实现
我需要我的朋友帮助我买一个东西
抽象主题类和真实主题类
可以将主题类理解为需要代理的“动作”或者说是一系列行为的集合
public interface IShop {
void buy();
}
public class Wo implements IShop{
@Override
public void buy() {
System.out.println("买");
}
}
代理类
public class Pengyou implements IShop{
private IShop mShop;
public Pengyou(IShop shop) {
mShop = shop;
}
@Override
public void buy() {
mShop.buy();
}
}
客户端
public class Client {
public static void main(String[] args) {
IShop wo = new Wo();
IShop pengyou = new Pengyou(wo);
pengyou.buy();
}
}
这里最后调用的buy其实还是wo的buy方法,最后输出还是“买”
动态代理
从编码的角度来说,代理模式分为静态代理和动态代理。上面的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件;而动态代理则是在代码运行时通过反射来动态地生成代理类的对象,并确定到底代理谁。
Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke方法。下面我们在上面静态代理的例子上做修改。首先创建动态代理类,代码如下所示:
package com.example.http;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicPurchasing implements InvocationHandler {
private Object obj;
public DynamicPurchasing(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws
Throwable {
Object result = method.invoke(obj, args);
if (method.getName().equals("buy")) {
System.out.println("Liuwangshu在买买买");
}
return result;
}
}
在动态代理类中我们声明了一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke方法中执行。接下来我们修改客户端类的代码:
public class Client(
public static void main(String[]args){
IShop wo=new Wo();//创建动态代理
DynamicPurchasing mDynamicPurchasing=new DynaicPurchasing(wo);
ClassLoader loader=wo.getclass().getClassLoader();//动态创建代理类
IShop purchasing=(IShop) Proxy.newProxyInstance(loader,new Class[]{IShop.class},mDynamicPurchasing);purchasing.buy();
}
3.代理模式的类型和优点
代理模式从编码的角度来说可以分为静态代理和动态代理,而从适用范围来讲则可分为以下4种类型。
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的实现隐藏。
- 虚拟代理:使用一个代理对象表示一个十分耗费资源的对象并在真正需要时才创建。
- 安全代理:用来控制真实对象访问时的权限。一般用于真实对象有不同的访问权限时。
- 智能指引:当调用真实的对象时,代理处理另外一些事,比如计算真实对象的引用计数,当该对象没有引用时,可以自动释放它;或者在访问一个实际对象时,检查是否已经能够锁定它,以确保其他对象不能改变它。
代理模式的优点主要有以下几点:
- 真实主题类就是实现实际的业务逻辑,不用关心其他非本职的工作。
- 真实主题类随时都会发生变化;但是因为它实现了公共的接口,所以代理类可以不做任何修改就能够使用。
装饰模式是结构型设计模式之一,其在不必改变类文件和使用继承的情况下,动态地扩展一个对象的功能,是继承的替代方案之一。它通过创建一个包装对象,也就是装饰来包裹真实的对象。
定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
装饰模式的结构图如图所示。在装饰模式中有如下角色。
- Component:抽象组件,可以是接口或是抽象类,即被装饰的最原始的对象。
- ConcreteComponent:组件具体实现类。即Component的具体实现类,被装饰的具体对象。
- Decorator:抽象装饰者。从外类来拓展Component类的功能,但对于Component来说无须知道Decorator的存在。在它的属性中必然有一个private变量指向Component(抽象组件)。
- ConcreteDecorator:装饰者的具体实现类。
装饰模式的简单实现
和名字一样很好理解,比如我会安卓,然后又学了前端和后台,这里
装饰模式在现实生活中有很多例子,比如给一个人穿上各种衣服,给一幅画涂色上框等等,这次我要举得例子有些不同,举一个武侠修炼武功的例子:杨过本身就会全真剑法,有两位武学前辈洪七公和欧阳锋分别传授杨过打狗棒法和蛤蟆功,这样杨过除了会全真剑法还会打狗棒法和蛤蟆功。
抽象组件
作为武侠肯定要会使用武功的,我们先定义一个武侠的抽象类,里面有使用武功的抽象方法:
public abstract class Swordsman {
/**
* Swordsman武侠有使用武功的抽象方法
*/
public abstract void attackMagic();
}
组件具体实现类(ConcreteComponent)
被装饰的具体对象,在这里就是被教授武学的具体的武侠,他就是杨过,杨过作为武侠当然也会武学,虽然不怎么厉害:
public class YangGuo extends Swordsman{
@Override
public void attackMagic() {
//杨过初始的武学是全真剑法
System.out.println("杨过使用全真剑法");
}
}
抽象装饰者(Decorator)
抽象装饰者保持了一个对抽象组件的引用,方便调用被装饰对象中的方法。在这个例子中就是武学前辈要持有武侠的引用,方便教授他武学并“融会贯通”:
public abstract class Master extends Swordsman{
private Swordsman mSwordsman;
public Master(Swordsman mSwordsman){
this.mSwordsman=mSwordsman;
}
@Override
public void attackMagic() {
mSwordsman.attackMagic();
}
}
装饰者具体实现类(ConcreteDecorator)
这个例子中用两个装饰者具体实现类,分别是洪七公和欧阳锋,他们负责来传授杨过新的武功:
public class HongQiGong extends Master {
public HongQiGong(Swordsman mSwordsman) {
super(mSwordsman);
}
public void teachAttackMagic(){
System.out.println("洪七公教授打狗棒法");
System.out.println("杨过使用打狗棒法");
}
@Override
public void attackMagic() {
super.attackMagic();
teachAttackMagic();
}
}
public class OuYangFeng extends Master {
public OuYangFeng(Swordsman mSwordsman) {
super(mSwordsman);
}
public void teachAttackMagic(){
System.out.println("欧阳锋教授蛤蟆功");
System.out.println("杨过使用蛤蟆功");
}
@Override
public void attackMagic() {
super.attackMagic();
teachAttackMagic();
}
}
客户端调用
经过洪七公和欧阳锋的教导,杨过除了初始武学全真剑法又学会了打狗棒法和蛤蟆功:
public class Client {
public static void main(String[] args){
//创建杨过
YangGuo mYangGuo=new YangGuo();
//洪七公教授杨过打狗棒法,杨过会了打狗棒法
HongQiGong mHongQiGong=new HongQiGong(mYangGuo);
mHongQiGong.attackMagic();
//欧阳锋教授杨过蛤蟆功,杨过学会了蛤蟆功
OuYangFeng mOuYangFeng=new OuYangFeng(mYangGuo);
mOuYangFeng.attackMagic();
}
}
装饰模式的优缺点和使用场景
优点
- 通过组合而非继承的方式,动态的来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
- 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
- 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
- 装饰链不能过长,否则会影响效率。
- 因为所有对象都是继承于Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),如果基类改变,势必影响对象的内部。
- 比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐,所以只在必要的时候使用装饰者模式。
使用场景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能可以动态的撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
装饰模式和代理模式
在上一篇文章设计模式之代理模式中我们讲到了代理模式,你会觉得代理模式和装饰模式有点像,都是持有了被代理或者被装饰对象的引用。它们两个最大的不同就是装饰模式对引用的对象增加了功能,而代理模式只是对引用对象进行了控制却没有对引用对象本身增加功能。
-
API,我们大多都会用到外观模式,它通过一个外观类使得整个系统的结构只有一个统一的高层接口,这样能降低用户的使用成本。
定义:为系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得子系统更加容易使用。
- Facade:外观类,知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象。
- Subsystem:子系统类,实现子系统的功能,处理外观类指派的任务,注意子系统类不含有外观类的引用。
外观模式的简单实现
首先我们把武侠张无忌当作一个系统,他作为一个武侠,他本身分为三个系统分别是招式、内功和经脉。
子系统类
/** * 子系统招式 */ public class ZhaoShi { public void TaiJiQuan(){ System.out.print("使用着招式太极拳"); } public void QiShangQuan(){ System.out.print("使用招式七伤拳"); } public void ShengHuo(){ System.out.print("使用招式圣火令"); } } /** * 子系统招式 */ public class ZhaoShi { public void TaiJiQuan(){ System.out.print("使用着招式太极拳"); } public void QiShangQuan(){ System.out.print("使用招式七伤拳"); } public void ShengHuo(){ System.out.print("使用招式圣火令"); } } /** * 子系统经脉 */ public class JingMai { public void jingmai(){ System.out.print("开启经脉"); } }
外观类
外观类就是张无忌,他负责将自己的招式、内功和经脉通过不同的情况合理的运用:
/** * 外观类张无忌 */ public class ZhangWuJi { private JingMai jingMai; private ZhaoShi zhaoShi; private NeiGong neiGong; public ZhangWuJi(){ jingMai=new JingMai(); zhaoShi=new ZhaoShi(); neiGong=new NeiGong(); } /** * 使用乾坤大挪移 */ public void Qiankun(){ jingMai.jingmai();//开启经脉 neiGong.QianKun();//使用内功乾坤大挪移 } /** * 使用七伤拳 */ public void QiShang(){ jingMai.jingmai(); //开启经脉 neiGong.JiuYang();//使用内功九阳神功 zhaoShi.QiShangQuan();//使用招式七伤拳 } }
客户端调用
public class Test { public static void main(String[] args){ ZhangWuJi zhangWuJi=new ZhangWuJi(); //张无忌使用乾坤大挪移 zhangWuJi.Qiankun(); //张无忌使用七伤拳 zhangWuJi.QiShang(); } }
外观模式本身就是将子系统的逻辑和交互隐藏起来,为用户提供一个高层次的接口,使得系统更加易用,同时也隐藏了具体的实现,这样即使具体的子系统发生了变化,用户也不会感知到。
外观模式使用场景
构建一个有层次结构的子系统时,使用外观模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,则可以让他们通过外观接口进行通信,减少子系统之间的依赖关系。
子系统往往会因为不断的重构演化而变得越来越复杂,大多数的模式使用时也会产生很多很小的类,这给外部调用他们的用户程序带来了使用的困难,我们可以使用外观类提供一个简单的接口,对外隐藏子系统的具体实现并隔离变化。
当维护一个遗留的大型系统时,可能这个系统已经非常难以维护和拓展,但因为它含有重要的功能,新的需求必须依赖于它,则可以使用外观类,来为设计粗糙或者复杂的遗留代码提供一个简单的接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
享元模式是结构型设计模式的一种,是池技术的重要实现方式,它可以减少应用程序创建的对象,降低程序内存的占用,提高程序的性能。
定义:使用共享对象有效的支持大量细粒度的对象
要求细粒度对象,那么不可避免地使得对象数量多且性质相近,这些对象分为两个部分:内部状态和外部状态。
内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变。
外部状态是对象依赖的一个标记是随环境改变而改变的并且不可共享的状态。
享元模式结构图如下图所示。
在享元模式中有如下角色:
- Flyweight:抽象享元角色,同时定义出对象的外部状态和内部状态的接口或者实现。
- ConcreteFlyweight:具体享元角色,实现抽象享元角色定义的业务。
- FlyweightFactory:享元工厂,负责管理对象池和创建享元对象。
享元对象的简单实现
某宝商城卖商品,如果每个用户下单都生成商品对象显然会耗费很多资源,如果赶上双11,那恐怖的订单量会生成很多商品对象,更何况商城卖的商品种类繁多,这样就极易会产生OOM。因此我们采用享元模式来对商品的创建进行优化。
抽象享元对象
抽象享元角色是一个商品接口,它定义了showGoodsPrice方法用来展示商品的价格:
public interface IGoods { public void showGoodsPrice(String name); }
具体享元对象
定义类Goods,它实现IGoods 接口,并实现了showGoodsPrice方法,如下所示。
public class Goods implements IGoods{ private String name;//名称 private String version;//版本 Goods(String name){ this.name=name; } @Override public void showGoodsPrice(String version) { if(version.equals("32G")){ System.out.println("价格为5199元"); }else if(version.equals("128G")){ System.out.println("价格为5999元"); } } }
其中name为内部状态,version为外部状态。showGoodsPrice方法根据version的不同会打印出不同的价格。
享元工厂
public class GoodsFactory { private static Map<String,Goods> pool=new HashMap<String, Goods>(); public static Goods getGoods(String name){ if(pool.containsKey(name)){ System.out.println("使用缓存,key为:"+name); return pool.get(name); }else{ Goods goods=new Goods(name); pool.put(name,goods); System.out.println("创建商品,key为:"+name); return goods; } } }
享元工厂GoodsFactory 用来创建Goods对象。通过Map容器来存储Goods对象,将内部状态name作为Map的key,以便标识Goods对象。如果Map容器中包含此key,则使用Map容器中存储的Goods对象,否则就新创建Goods对象,并放入Map容器中。
客户端调用
客户端中调用GoodsFactory的getGoods方法来创建Goods对象,并调用Goods 的showGoodsPrice方法来显示产品的价格,如下所示。
public class Client { public static void main(String[]args) { Goods goods1=GoodsFactory.getGoods("iphone7"); goods1.showGoodsPrice("32G"); Goods goods2=GoodsFactory.getGoods("iphone7"); goods2.showGoodsPrice("32G"); Goods goods3=GoodsFactory.getGoods("iphone7"); goods3.showGoodsPrice("128G"); } }
运行结果为: 创建商品,key为:iphone7 价格为5199元 使用缓存,key为:iphone7 价格为5199元 使用缓存,key为:iphone7 价格为5999元
享元模式的使用场景
- 系统中存在大量的相似对象。
- 需要缓冲池的场景。
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
行为型设计模式
行为型模式主要处理类或对象如何交互及如何分配职责。它共有11种模式:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式和解释器模式。本节会介绍策略模式、模板方法模式和观察者模式。
当我们写代码时总会遇到一种情况,就是我们会有很多选择,由此衍生出很多的if…else或者case。如果每个条件语句中包含了一个简单的逻辑,那还比较容易处理;但如果在一个件语句中又包含了多个条件语句,就会使得代码变得臃肿,维护的成本也会加大,这显然违了开放封闭原则。本节我们将讲解策略模式,看看它是怎么解决如上所说的问题的。
定义:定义一系列的算法,把每一个算法封装起来,并且使他可相互替代,策略模式可独立于使用他的客户独立变化
- Context:上下文角色,用来操作策略的上下文环境,起到承上启下的作用,屏蔽高层模块对策略、算法的直接访问。
- Stragety:抽象策略角色,策略、算法的抽象,通常为接口。
- ConcreteStragety:具体的策略实现。
策略方法的简单实现:
张无忌作为一个大侠会遇到很多对手,如果每遇到一个对手,他都用自己最厉害的武功去应战,这显然是不明智的。于是张无忌想出了3种应战的策略,分别对付3个实力层次的对手。
1.定义策略接口
public interface FightingStrategy { public void fighting(); }
2.具体策略实现
public class WeakReivalStrategy implements FightingStrategy { @Override public void fighting() { System.out.println("对手弱,使用太极剑"); } }public class CommonReivalStrategy implements FightingStrategy { @Override public void fighting() { System.out.println("对手一般,使用圣火令"); } }public class StrongReivalStrategy implements FightingStrategy { @Override public void fighting() { System.out.println("对手强,使用乾坤大挪移"); } }
3.上下文角色
public class Content { private FightingStrategy fightingStrategy; public void setFightingStrategy(FightingStrategy fightingStrategy) { this.fightingStrategy = fightingStrategy; } public void fighting() { fightingStrategy.fighting(); } }
4.客户端调用
public class ZhangWuJi { public static void main(String[] args) { Context context; //张无忌遇到对手宋青书,采用对较弱对手的策略 context = new Context(new WeakRivalStrategy()); context.fighting(); //张无忌遇到对手灭绝师太,采用对普通对手的策略 context = new Context(new ComonRivalStrategy()); context.fighting();//张无忌遇到对手成昆,采用对强大对手的策略 context = new Context(new StrongRivalStrategy()); context.fighting(); } }
上面只是举了一个简单的例子,其实情况会很多:比如遇到普通的对手,也不能完全用圣火令神功;比如当遇到周芷若或赵敏时就需要手下留情,采用太极剑;又比如遇到强劲的对手张三丰时,由于张三丰是自己的师公,因此也不能使用乾坤大挪移。类似这样的情况会很多,这样在每个策略类中可能会出现很多条件语句。但是试想一下如果我们不用策略模式来封装这些条件语句,那么可能会导致一个条件语句中又包含了多个条件语句,这样会使代码变得臃肿,维护的成本也会加大。
2.策略模式的使用场景和优缺点
- 使用场景:
对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
针对同一类型问题的多种处理方式,仅具体行为有差别时。
在一个类中定义了很多行为,而且这些行为在这个类里的操作以多个条件语句的形式出现。策略模式将相关的条件分支移入它们各自的Strategy类中,以代替这些条件语句。
- 优点:
使用策略模式可以避免使用多重条件语句。多重条件语句不易维护,而且易出错。
易于拓展。当需要添加一个策略时,只需要实现接口就可以了。
- 缺点:
每一个策略都是一个类,复用性小。如果策略过多,类的数量会增多。
上层模块必须知道有哪些策略,才能够使用这些策略,这与迪米特原则相违背。
在软件开发中,有时会遇到类似的情况,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
- AbstractClass:抽象类,定义了一套算法。
- ConcreteClass:具体实现类。
简单实现
创建抽象类,定义算法框架
一个武侠要战斗的时候,也有一套固定的通用模式,那就是运行内功、开通经脉、准备武器和使用招式,我们把这些用代码表示就是:
public abstract class AbstractSwordsman { //该方法为final,防止算法框架被覆写 public final void fighting(){ //运行内功,抽象方法 neigong(); //调整经脉,具体方法 meridian(); //如果有武器则准备武器 if(hasWeapons()) { weapons(); } //使用招式 moves(); //钩子方法 hook(); } //空实现方法 protected void hook(){} protected abstract void neigong(); protected abstract void weapons(); protected abstract void moves(); protected void meridian(){ System.out.println("开通正经与奇经"); } /** * 是否有武器,默认是有武器的,钩子方法 * @return */ protected boolean hasWeapons(){ return true; } }
需要注意的是这个抽象类包含了三种类型的方法,分别是抽象方法、具体方法和勾子方法。抽象方法是交由子类去实现,具体方法则在父类实现了子类公共的方法实现,在上面的例子就是武侠开通经脉的方式都一样,所以就在具体方法中实现。钩子方法则分为两类,第一类是15行,它有一个空实现的方法,子类可以视情况来决定是否要覆盖它;第二类则是第9行,这类钩子方法的返回类型通常是bool类型的,一般用于对某个条件进行判断,如果条件满足则执行某一步骤,否则将不执行。
定义具体实现类
本文就拿张无忌、张三丰来作为例子:
public class ZhangWuJi extends AbstractSwordsman { @Override protected void neigong() { System.out.println("运行九阳神功"); } @Override protected void weapons() { } @Override protected void moves() { System.out.println("使用招式乾坤大挪移"); } @Override protected boolean hasWeapons() { return false; } }
张无忌没有武器所以hasWeapons方法返回false,这样也不会走weapons方法了。
public class ZhangSanFeng extends AbstractSwordsman { @Override protected void neigong() { System.out.println("运行纯阳无极功"); } @Override protected void weapons() { System.out.println("使用真武剑"); } @Override protected void moves() { System.out.println("使用招式神门十三剑"); } @Override protected void hook() { System.out.println("突然肚子不舒服,老夫先去趟厕所"); } }
最后张三丰突然肚子不舒服所以就实现了钩子方法hook。
客户端调用
public class Client { public static void main(String[] args) { ZhangWuJi zhangWuJi=new ZhangWuJi(); zhangWuJi.fighting(); ZhangSanFeng zhangSanFeng=new ZhangSanFeng(); zhangSanFeng.fighting(); } }
运行结果: 运行九阳神功 开通正经与奇经 使用招式乾坤大挪移 运行纯阳无极功 开通正经与奇经 使用真武剑 使用招式神门十三剑 突然肚子不舒服,老夫先去趟厕所
模版方法模式的优缺点和使用场景
优点
模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
子类实现算法的某些细节,有助于算法的扩展。
缺点
每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
使用场景
各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
面对重要复杂的算法,可以把核心算法设计为模版方法,周边相关细节功能则有各个子类实现。
需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
-
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
观察者模式简单实现
观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。好了我们来看看用代码如何实现:
抽象观察者:
public interface Observer { public void update(String message); }
具体观察者:
微信用户是观察者,里面实现了更新的方法:
public class WeixinUser implements Observer { // 微信用户名 private String name; public WeixinUser(String name) { this.name = name; } @Override public void update(String message) { System.out.println(name + "-" + message); } }
抽象被观察者:
抽象主题,提供了attach、detach、notify三个方法:
public interface Subject { /** * 增加订阅者 * @param observer */ public void attach(Observer observer); /** * 删除订阅者 * @param observer */ public void detach(Observer observer); /** * 通知订阅者更新消息 */ public void notify(String message); }
具体被观察者:
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:
public class SubscriptionSubject implements Subject { //储存订阅公众号的微信用户 private List<Observer> weixinUserlist = new ArrayList<Observer>(); @Override public void attach(Observer observer) { weixinUserlist.add(observer); } @Override public void detach(Observer observer) { weixinUserlist.remove(observer); } @Override public void notify(String message) { for (Observer observer : weixinUserlist) { observer.update(message); } } }
客户端调用:
public class Client { public static void main(String[] args) { SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject(); //创建微信用户 WeixinUser user1=new WeixinUser("杨影枫"); WeixinUser user2=new WeixinUser("月眉儿"); WeixinUser user3=new WeixinUser("紫轩"); //订阅公众号 mSubscriptionSubject.attach(user1); mSubscriptionSubject.attach(user2); mSubscriptionSubject.attach(user3); //公众号更新发出消息给订阅的微信用户 mSubscriptionSubject.notify("刘望舒的专栏更新了"); } }
杨影枫-刘望舒的专栏更新了
月眉儿-刘望舒的专栏更新了
紫轩-刘望舒的专栏更新了
使用观察者模式的场景和优缺点
使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Android 设计模式
发表评论 取消回复