单一职责原则(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 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);
}
}
GoF提出的设计模式总共有23种,根据目的准则分类,分为三大类。
另外,随着设计模式的发展也涌现出很多新的设计模式:它们分别是规格模式、对象池模式、雇工模式、黑板模式和空对象模式等。
定义:保证只有一个类一个实例,并提供一个访问它的全局访问节点
客户端通过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();
}
}
简单工厂模式(又叫作静态工厂方法模式),其属于创建型设计模式,但是并不属于23种GoF设计模式之一。提到它是为了让大家能够更好地理解后面讲到的工厂方法模式。
定义:简单工厂模式属于创建型设计模式,其又被称为静态工厂方法模式,这是由一个工厂对象决定创建出哪一种产品类的实例。
这里我们用生产计算机来举例,假设有一个计算机的代工生产商,它目前已经可以代工生产联想计算机了。随着业务的拓展,这个代工生产商还要生产惠普和华硕的计算机。这样我们就需要用一个单独的类来专门生产计算机,这就用到了简单工厂模式。下面我们来实现简单工厂模式。
我们创建一个计算机的抽象产品类,其有一个抽象方法用于启动计算机,如下所示:
public abstract class Computer {
// 产品的抽象方法,由具体产品类实现
public abstract void start();
}
接着我们创建各个品牌的计算机,其都继承了自己的父类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("华硕计算机启动");
}
}
接下来创建一个工厂类,它提供了一个静态方法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;
}
}
客户端调用工厂类,传入"hp"生产出惠普计算机并调用该计算机对象的start方法,如下所示:
public class CreatComputer {
public static void main(String[]args){
ComputerFactory.create("hp").start();
}
}
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
在工厂方法模式中有如下角色。
public abstract class ComputerFactory {
public abstract T createComputer(Class clazz);
}
继承自抽象工厂,通过反射生成不同厂家的计算器:
package com.example.http;
public class GDComputerFactor extends ComputerFactory {
@Override
public T createComputer(Class clazz) {
Computer computer = null;
String computerName = clazz.getName();
try {
computer = (Computer) Class.forName(computerName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) computer;
}
}
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();
}
}
建造者模式,也叫生成器模式,他是创建一个复杂对象的创建型设计模式,将构建复杂对象的过程和他的部件解耦,使得构建过程和部件的表示分离开来
定义:将一个复杂的对象构建与他的表示分离,使得同样的构建过程可以创建不同的表示
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);
}
}
// 抽象建造者
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");
}
}
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
// 构建最终产品
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
// 客户端
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)。如果需要更大的灵活性使用工厂方法模式
结构型设计模式将从程序的结构上解决模块之间的耦合问题,它包括适配器模式、代理模式、装饰模式、外观模式、桥接模式、组合模式和享元模式。本节会介绍代理模式、装饰模式、外观模式和享元模式。
定义:为其他对象提供一种代理以控制对这个对象的访问
我需要我的朋友帮助我买一个东西
可以将主题类理解为需要代理的“动作”或者说是一系列行为的集合
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种类型。
代理模式的优点主要有以下几点:
装饰模式是结构型设计模式之一,其在不必改变类文件和使用继承的情况下,动态地扩展一个对象的功能,是继承的替代方案之一。它通过创建一个包装对象,也就是装饰来包裹真实的对象。
定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
装饰模式的结构图如图所示。在装饰模式中有如下角色。
和名字一样很好理解,比如我会安卓,然后又学了前端和后台,这里
装饰模式在现实生活中有很多例子,比如给一个人穿上各种衣服,给一幅画涂色上框等等,这次我要举得例子有些不同,举一个武侠修炼武功的例子:杨过本身就会全真剑法,有两位武学前辈洪七公和欧阳锋分别传授杨过打狗棒法和蛤蟆功,这样杨过除了会全真剑法还会打狗棒法和蛤蟆功。
作为武侠肯定要会使用武功的,我们先定义一个武侠的抽象类,里面有使用武功的抽象方法:
public abstract class Swordsman {
/**
* Swordsman武侠有使用武功的抽象方法
*/
public abstract void attackMagic();
}
被装饰的具体对象,在这里就是被教授武学的具体的武侠,他就是杨过,杨过作为武侠当然也会武学,虽然不怎么厉害:
public class YangGuo extends Swordsman{
@Override
public void attackMagic() {
//杨过初始的武学是全真剑法
System.out.println("杨过使用全真剑法");
}
}
抽象装饰者保持了一个对抽象组件的引用,方便调用被装饰对象中的方法。在这个例子中就是武学前辈要持有武侠的引用,方便教授他武学并“融会贯通”:
public abstract class Master extends Swordsman{
private Swordsman mSwordsman;
public Master(Swordsman mSwordsman){
this.mSwordsman=mSwordsman;
}
@Override
public void attackMagic() {
mSwordsman.attackMagic();
}
}
这个例子中用两个装饰者具体实现类,分别是洪七公和欧阳锋,他们负责来传授杨过新的武功:
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();
}
}
优点
缺点
使用场景
在上一篇文章设计模式之代理模式中我们讲到了代理模式,你会觉得代理模式和装饰模式有点像,都是持有了被代理或者被装饰对象的引用。它们两个最大的不同就是装饰模式对引用的对象增加了功能,而代理模式只是对引用对象进行了控制却没有对引用对象本身增加功能。
当我们开发Android的时候,无论是做SDK还是封装API,我们大多都会用到外观模式,它通过一个外观类使得整个系统的结构只有一个统一的高层接口,这样能降低用户的使用成本。
定义:为系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得子系统更加容易使用。
首先我们把武侠张无忌当作一个系统,他作为一个武侠,他本身分为三个系统分别是招式、内功和经脉。
/**
* 子系统招式
*/
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();
}
}
外观模式本身就是将子系统的逻辑和交互隐藏起来,为用户提供一个高层次的接口,使得系统更加易用,同时也隐藏了具体的实现,这样即使具体的子系统发生了变化,用户也不会感知到。
构建一个有层次结构的子系统时,使用外观模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,则可以让他们通过外观接口进行通信,减少子系统之间的依赖关系。
子系统往往会因为不断的重构演化而变得越来越复杂,大多数的模式使用时也会产生很多很小的类,这给外部调用他们的用户程序带来了使用的困难,我们可以使用外观类提供一个简单的接口,对外隐藏子系统的具体实现并隔离变化。
当维护一个遗留的大型系统时,可能这个系统已经非常难以维护和拓展,但因为它含有重要的功能,新的需求必须依赖于它,则可以使用外观类,来为设计粗糙或者复杂的遗留代码提供一个简单的接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
享元模式是结构型设计模式的一种,是池技术的重要实现方式,它可以减少应用程序创建的对象,降低程序内存的占用,提高程序的性能。
定义:使用共享对象有效的支持大量细粒度的对象
要求细粒度对象,那么不可避免地使得对象数量多且性质相近,这些对象分为两个部分:内部状态和外部状态。
内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变。
外部状态是对象依赖的一个标记是随环境改变而改变的并且不可共享的状态。
享元模式结构图如下图所示。
在享元模式中有如下角色:
某宝商城卖商品,如果每个用户下单都生成商品对象显然会耗费很多资源,如果赶上双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 pool=new HashMap();
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。如果每个条件语句中包含了一个简单的逻辑,那还比较容易处理;但如果在一个件语句中又包含了多个条件语句,就会使得代码变得臃肿,维护的成本也会加大,这显然违了开放封闭原则。本节我们将讲解策略模式,看看它是怎么解决如上所说的问题的。
定义:定义一系列的算法,把每一个算法封装起来,并且使他可相互替代,策略模式可独立于使用他的客户独立变化
张无忌作为一个大侠会遇到很多对手,如果每遇到一个对手,他都用自己最厉害的武功去应战,这显然是不明智的。于是张无忌想出了3种应战的策略,分别对付3个实力层次的对手。
public interface FightingStrategy {
public void fighting();
}
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("对手强,使用乾坤大挪移");
}
}
public class Content {
private FightingStrategy fightingStrategy;
public void setFightingStrategy(FightingStrategy fightingStrategy) {
this.fightingStrategy = fightingStrategy;
}
public void fighting() {
fightingStrategy.fighting();
}
}
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类中,以代替这些条件语句。
使用策略模式可以避免使用多重条件语句。多重条件语句不易维护,而且易出错。
易于拓展。当需要添加一个策略时,只需要实现接口就可以了。
每一个策略都是一个类,复用性小。如果策略过多,类的数量会增多。
上层模块必须知道有哪些策略,才能够使用这些策略,这与迪米特原则相违背。
在软件开发中,有时会遇到类似的情况,某个方法的实现需要多个步骤,其中有些步骤是固定的,而有些步骤并不固定,存在可变性。为了提高代码的复用性和系统的灵活性,可以使用模板方法模式来应对这类情况。
定义:定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
一个武侠要战斗的时候,也有一套固定的通用模式,那就是运行内功、开通经脉、准备武器和使用招式,我们把这些用代码表示就是:
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)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。好了我们来看看用代码如何实现:
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 weixinUserlist = new ArrayList();
@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中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实