设计模式概述
设计模式是从大量的实践中总结和理论化之后优选的代码结构 、编程风格 、以及问题解决思路。它们如同经典棋谱,不同的棋局不同棋局对应不同"套路",帮助我们高效应对各种编程挑战。
设计原则是一些通用的设计指导方针,它们提供了如何设计一个优秀的软件系统的基本思想和规则。指导着设计者如何组织代码以实现高内聚、低耦合、易扩展 和易维护的软件系统。
- 设计模式则是在特定情况下解决常见问题的经验性解决方案,它们提供了如何实现这些设计原则的具体方法。
- 设计模式往往是在满足设计原则的基础上被应用的。设计模式可以看作是实现设计原则的一种具体方式。
六大设计原则
- 单一职责原则:一个类应该只有一个引起它变化的原因。换句话说,一个类应该只有一项职责。确保类内聚性,降低耦合性。
- 开闭原则:软件实体(如类、模块、函数)应对扩展开放,对修改关闭,优先通过扩展现有代码实现新功能,而非修改已有代码。
- 里氏替换原则:子类应能替换父类而不影响程序正确性。子类只能扩展父类功能,不能改变已有行为。所有引用父类的地方,必须能使用子类的对象。
- 接口隔离原则:客户端不应该依赖于它不需要的接口。一个类应该只提供它需要的接口,而不应该强迫客户端依赖于它不需要的接口。
- 依赖倒置原则:高层模块不应依赖低层模块,二者应依赖于抽象。抽象不应依赖具体实现,具体实现应依赖抽象。
- 迪米特法则 :一个对象应该对其他对象保持最少的了解。换句话说,一个对象只应该与它直接相互作用的对象发生交互,而不应该与其它任何对象发生直接的交互。这样可以降低类之间的耦合性,提高系统的灵活性和可维护性。
设计模式分类
设计模式主要有以下三种:
- 创建型模式,解决对象创建问题。共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,主要解决对象组合问题。共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,主要解决对象之间的交互问题。共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
5种创建型模式详解
创建型模式关注对象的创建与组装,通过抽象化和解耦对象的创建过程,以提高系统灵活性与可扩展性。
单例(Singleton)设计模式
单例模式确保一个类只有一个实例,并提供全局访问点。
实现思路 :如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private ,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法 以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
优点:
降低系统性能开销,特别适用于资源消耗大的对象(如配置、数据库连接等)。
应用场景
全局资源管理(如配置文件、日志记录器、数据库连接池等)。
➢网站的计数器,一般也是单例模式实现,否则难以同步。
➢应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一 直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
➢数据库连接池的设计一般也是采用单例模式,如果需要频繁地与数据库交互,使用单例模式可以确保只有一个数据库连接实例,从而减少数据库连接等数量,提高运用程序的性能。
➢项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
➢Application也是单例的典型应用
➢Windows的Task Manager (任务管理器)就是很典型的单例模式
➢Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
代码实现
饿汉式
bash
- 类初始化时直接创建单例对象,而类初始化过程是没有线程安全问题的
class Bank{
//1.私有化类的构造器
private Bank(){}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
懒汉式
bash
-- 延迟创建对象,第一次调用getinstance方法再创建对象
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance( ){
if(instance == null){
instance = new Order();
}
return instance;
}
}
饿汉式VS懒汉式
饿汉式:
- 坏处:对象加载时间过长。
- 好处:饿汉式是线程安全的
懒汉式: - 好处:延迟对象的创建。
- 目前的写法坏处:线程不安全。--->到多线程内容时,再修改
懒汉式-线程安全版
bash
class Order{
private Order(){
}
private static Order instance = null;
//同步代码块
public static Order getInstance( ){
//方式一:效率稍差
synchronized(Order.class){
if(instance == null){
instance = new Order();
}
return instance;
}
//方式二:效率稍高 当实例没有创建时需要加锁。双重检查锁
if (instance == null){
synchronized(Order.class){
if(instance == null){
instance = new Order();
}
return instance;
}
}
//方法三:同步方法(锁粒度大,比较粗糙)
public static synchronized Order getInstance( ){
if(instance == null){
instance = new Order();
}
return instance;
}
}
工厂模式
工厂模式通过提供一个接口来创建对象,隐藏具体实现细节。
应用场景
1.对象的创建过程比较复杂,需要进行封装 :如果创建一个对象需要进行复杂的初始化过程,或者需要从多个地方获取数据才能创建对象,那么使用工厂模式可以将这些过程封装起来,让客户端代码更加简洁和易于理解。
2.需要动态扩展或修改对象的创建过程 :如果需要增加或修改某个对象的创建过程,而又不希望对客户端代码产生影响,那么使用工厂模式可以很方便地实现这个需求。
3.需要统一管理对象的创建 :如果需要统一管理对象的创建过程,或者需要对创建的对象进行某些统一的处理,那么使用工厂模式可以很好地实现这个需求。
4.需要根据不同的条件创建不同的对象:如果需要根据不同的条件来创建不同类型的对象,那么使用工厂模式可以很方便地实现这个需求。
代码实现
通过一个工厂类来封装对象的创建过程,客户端只需要告诉工厂类需要创建哪种类型的对象即可。将对象的创建过程与客户端代码分离开来,使代码更加灵活和易于扩展
bash
// 定义产品接口
public interface Product {
void operation();
}
// 具体产品类A
public class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation.");
}
}
// 具体产品类B
public class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation.");
}
}
// 工厂类
public class SimpleFactory {
public static Product createProduct(String type) {
if ("A".equals(type)) {
return new ConcreteProductA();
} else if ("B".equals(type)) {
return new ConcreteProductB();
} else {
throw new IllegalArgumentException("Invalid product type.");
}
}
}
客户端可以通过调用
bash
SimpleFactory.createProduct方法来创建不同类型的产品对象
Product productA = SimpleFactory.createProduct("A");
productA.operation(); // 输出 "ConcreteProductA operation."
Product productB = SimpleFactory.createProduct("B");
productB.operation(); // 输出 "ConcreteProductB operation."
使用小结
在Java中,工厂模式广泛应用于各种框架和类库中,例如JDBC中的DataSource工厂、Spring框架中的Bean工厂、MyBatis框架中的SqlSessionFactory等等。
抽象工厂模式 Abstract factoryPattern
提供一个接口,用于创建相关或依赖对象的序列,而不需要指定实际实现类。
在Java中,抽象工厂模式通常包括以下几个角色:
- AbstractFactory:抽象工厂,声明了创建产品对象的方法。
- ConcreteFactory:具体工厂,实现了创建产品对象的方法。
- AbstractProduct:抽象产品,声明了产品对象的共性接口。
- Product:具体产品,实现了抽象产品中的抽象方法,构成产品族。
- Client :客户端,通过调用工厂类的方法创建产品对象。
抽象工厂和工厂模式都是创建对象的设计模式,它们的主要区别什么呢? - 目的不同:工厂模式用于创建一类产品对象的实例,而抽象工厂模式用于创建一组相关的产品对象实例。
- 实现方式不同:工厂模式中只有一个工厂类,该类负责创建所有的产品对象;而抽象工厂模式中有多个工厂类,每个工厂类负责创建一组相关的产品对象。
- 范围不同:工厂模式通常用于创建单个对象,而抽象工厂模式通常用于创建一组相关的对象
使用场景
- 当需要创建一组相关的产品对象,这些产品对象之间有共同的约束条件,需要一起使用时,可以使用抽象工厂模式。
- 当系统需要独立于产品的创建,组装和表示时,可以使用抽象工厂模式
- 当系统需要支持多个不同的产品族,且不希望依赖于具体产品类时,可以使用抽象工厂模式。
- 当系统需要在运行时切换不同的产品族时,可以使用抽象工厂模式。
- 当需要遵循"开闭原则",即系统需要扩展新的产品族时,不需要修改已有代码,可以使用抽象工厂模式。
代码实现
假设有一个在线商店需要提供两种支付方式:信用卡支付和网银支付,每种支付方式又包含两种具体的实现:Visa 信用卡支付和 MasterCard 信用卡支付,以及支付宝网银支付和微信网银支付。
首先,定义支付方式的接口:
bash
// 信用卡支付接口
public interface CreditCardPayment {
void pay(double amount);
}
// 网银支付接口
public interface OnlineBankingPayment {
void pay(double amount);
}
接着,定义具体的实现:
bash
// Visa 信用卡支付
public class VisaCreditCardPayment implements CreditCardPayment {
@Override
public void pay(double amount) {
System.out.println("Visa credit card payment: $" + amount);
}
}
// MasterCard 信用卡支付
public class MasterCardCreditCardPayment implements CreditCardPayment {
@Override
public void pay(double amount) {
System.out.println("MasterCard credit card payment: $" + amount);
}
}
// 支付宝网银支付
public class AlipayOnlineBankingPayment implements OnlineBankingPayment {
@Override
public void pay(double amount) {
System.out.println("Alipay online banking payment: ¥" + amount);
}
}
// 微信网银支付
public class WeChatOnlineBankingPayment implements OnlineBankingPayment {
@Override
public void pay(double amount) {
System.out.println("WeChat online banking payment: ¥" + amount);
}
}
然后,定义抽象工厂类
bash
// 抽象支付工厂
public abstract class PaymentFactory {
public abstract CreditCardPayment createCreditCardPayment();
public abstract OnlineBankingPayment createOnlineBankingPayment();
}
定义具体的支付工厂类:
bash
// Visa 信用卡支付工厂
public class VisaPaymentFactory extends PaymentFactory {
@Override
public CreditCardPayment createCreditCardPayment() {
return new VisaCreditCardPayment();
}
@Override
public OnlineBankingPayment createOnlineBankingPayment() {
return new AlipayOnlineBankingPayment();
}
}
// MasterCard 信用卡支付工厂
public class MasterCardPaymentFactory extends PaymentFactory {
@Override
public CreditCardPayment createCreditCardPayment() {
return new MasterCardCreditCardPayment();
}
@Override
public OnlineBankingPayment createOnlineBankingPayment() {
return new WeChatOnlineBankingPayment();
}
}
最后,使用抽象工厂来创建支付对象
bash
public class Client {
public static void main(String[] args) {
// 选择 Visa 信用卡支付工厂
PaymentFactory paymentFactory = new VisaPaymentFactory();
// 创建 Visa 信用卡支付对象
CreditCardPayment creditCardPayment = paymentFactory.createCreditCardPayment();
creditCardPayment.pay(100);
// 创建支付宝网银支付对象
OnlineBankingPayment onlineBankingPayment = paymentFactory.createOnlineBankingPayment();
onlineBankingPayment.pay(200);
// 选择 MasterCard 信用卡支付工厂
PaymentFactory paymentFactory2 = new MasterCardPaymentFactory();
// 创建 MasterCard 信用卡支付对象
CreditCardPayment creditCardPayment2 = paymentFactory2.createCreditCardPayment();
creditCardPayment2.pay(100);
// 创建微信网银支付对象
OnlineBankingPayment onlineBankingPayment2 = paymentFactory2.createOnlineBankingPayment();
onlineBankingPayment2.pay(200);
}
使用小结
Java 抽象工厂模式在很多框架和应用程序中都有广泛的应用。以下是一些具体的应用:
1.Java 数据库连接框架 JDBC 中,使用抽象工厂模式来创建连接对象,例如 Connection、Statement 等。
2.Java 中的 XML 处理器 DOM 和 SAX,也使用抽象工厂模式来创建解析器和生成器对象。
3.Java 中的 Java Cryptography Architecture (JCA) 也使用抽象工厂模式,用于创建加密算法和密钥生成器对象。
总之,Java 抽象工厂模式可以在许多应用程序和框架中找到,它可以帮助您更好地组织和管理代码,提高代码的可扩展性和灵活性。
建造者模式
将复杂对象的创建与表示分离,使得同样的构建过程可以创建不同的表示。
原型模式 Prototype Pattern
通过克隆来创建对象,避免了通过 new 关键字显示调用构造函数的开销。
使用场景
- 当对象创建的过程比较耗时或者比较复杂,例如需要进行复杂的计算或者涉及到网络请求等操作,可以使用原型模式来避免重复的初始化过程。
- 当需要创建的对象需要和其他对象进行协同工作时,例如需要创建一个包含多个对象的组合对象,可以使用原型模式来复制一个已有的组合对象,然后进行修改来创建新的组合对象。
- 当需要动态地增加或者删除一些对象时,可以使用原型模式来复制一个已有的对象,然后进行修改来创建新的对象。
- 当需要保护对象的复杂状态时,例如当一个对象的创建需要大量的数据初始化时,可以使用原型模式来保护这些数据,避免因为对象的复制而产生意外的副作用。
代码实现
bash
// 定义一个原型接口
interface Prototype {
public Prototype clone();
}
// 具体的原型类
class ConcretePrototype implements Prototype {
public Prototype clone() {
return new ConcretePrototype();
}
}
// 客户端代码
class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype();
Prototype clone = prototype.clone();
}
}
使用小结
1.Java中的Object类实现了Cloneable接口,这就意味着Java中的任何对象都可以实现原型模式。通过实现Cloneable接口,并重写Object类中的clone()方法,可以实现原型模式。例如 ArrayList、HashMap 等集合类都实现了Cloneable 接口,可以通过复制现有对象来创建新的对象。
2.Java中的线程池也是使用了原型模式,线程池中的每个线程都是从原型线程中复制而来,而不是每次创建新的线程。