常见设计模式
设计模式是软件开发中解决常见问题的可重用解决方案。它们不仅提高了代码的可读性和可维护性,还能让开发过程更加高效。在这篇博客中,我将详细探讨八种常见的设计模式: 原型模式、 单例模式、 装饰器模式、 代理模式、 工厂方法模式、 观察者模式、 策略模式和 适配器模式。
1. 原型模式(Prototype Pattern)
定义
原型模式是一种创建型设计模式,通过复制已有对象(原型)来创建新对象,而不是通过构造函数实例化。这种方式可以减少创建对象的开销,尤其在对象初始化复杂或资源密集时。
用途
- 当对象的创建成本较高(如需要大量计算或从数据库加载数据)时。
- 当需要创建的对象与已有对象相似时。
- 避免子类化的复杂性。
示例场景
想象我们正在开发一款热门的多人在线游戏,玩家可以在游戏中创建和定制自己的角色,比如战士、法师或弓箭手。每个角色都有复杂的属性,如装备、技能树、外观和动画效果,初始化这些属性需要从服务器加载大量数据,耗时且占用资源。为了提升性能,我们决定使用一个“角色模板”(原型),比如一个预配置的“标准战士”,当玩家创建新战士时,系统直接克隆这个模板,并允许玩家调整角色的外观或技能,而无需每次都从头加载所有数据。这种方式不仅加快了角色创建速度,还让玩家能快速进入游戏。
代码示例(Java)
import java.util.Objects;
// 原型接口
interface Shape extends Cloneable {
Shape clone();
void draw();
}
// 具体原型
class Circle implements Shape {
private String type = "circle";
private String color = "black";
private int radius = 10;
@Override
public Shape clone() {
try {
return (Shape) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " " + type + " with radius " + radius);
}
// Getters and setters
public void setColor(String color) {
this.color = color;
}
public void setRadius(int radius) {
this.radius = radius;
}
}
// 测试
public class PrototypeTest {
public static void main(String[] args) {
Circle shapePrototype = new Circle();
Circle circle1 = (Circle) shapePrototype.clone();
circle1.setColor("red");
circle1.draw(); // 输出: Drawing a red circle with radius 10
Circle circle2 = (Circle) shapePrototype.clone();
circle2.setRadius(20);
circle2.draw(); // 输出: Drawing a black circle with radius 20
}
}
优点与缺点
- 优点:减少对象创建的开销,支持灵活的对象复制。
- 缺点:深拷贝可能复杂,尤其是当对象包含循环引用时。
2. 单例模式(Singleton Pattern)
定义
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。
用途
- 需要全局唯一实例的场景,如日志管理器、数据库连接池或配置管理。
- 控制对共享资源的访问。
示例场景
设想我们正在开发一个智能家居系统,控制家中的灯光、空调和安防设备。系统中需要一个中央控制面板,负责管理所有设备的设置,比如亮度、温度和警报状态。这个控制面板必须是唯一的,因为多个控制面板可能会导致设置冲突(比如一个面板将灯光调暗,另一个却调亮)。通过单例模式,我们就可以确保系统中只有一个控制面板实例,所有的设备操作都通过这个唯一的面板进行协调,保持状态一致,让用户轻松掌控智能家居。
代码示例(Java)
import java.util.HashMap;
import java.util.Map;
// 单例类
public class ConfigManager {
private static volatile ConfigManager instance;
private Map<String, String> settings = new HashMap<>();
private ConfigManager() {
// 防止通过反射创建实例
if (instance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance.");
}
settings.put("theme", "light");
settings.put("language", "en");
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
public String getSetting(String key) {
return settings.get(key);
}
public void setSetting(String key, String value) {
settings.put(key, value);
}
}
// 测试
class SingletonTest {
public static void main(String[] args) {
ConfigManager config1 = ConfigManager.getInstance();
config1.setSetting("theme", "dark");
System.out.println(config1.getSetting("theme")); // 输出: dark
ConfigManager config2 = ConfigManager.getInstance();
System.out.println(config2.getSetting("theme")); // 输出: dark
System.out.println(config1 == config2); // 输出: true
}
}
优点与缺点
- 优点:节省资源,统一访问点。
- 缺点:可能导致全局状态问题,难以测试和扩展。
3. 装饰器模式(Decorator Pattern)
定义
装饰器模式是一种结构型设计模式,允许动态地为对象添加新功能,而无需修改其代码。它通过包装原对象来扩展功能。
用途
- 需要在不修改现有代码的情况下扩展对象功能。
- 实现功能的动态组合。
示例场景
想象一下我们在经营一家高端咖啡店,顾客可以根据喜好定制咖啡。比如,有人想要一杯基础拿铁,有人则想在拿铁中加入香草糖浆、焦糖酱和奶泡,甚至还想加一勺巧克力粉。每种配料都会增加价格并改变咖啡的描述。直接修改基础咖啡的代码来支持所有组合会让代码变得臃肿且难以维护。使用装饰器模式,你可以动态地为咖啡“添加”配料,每次添加都自动更新价格和描述,让顾客的定制体验既灵活又个性化。
代码示例(Java)
// 咖啡接口
interface Coffee {
double cost();
String description();
}
// 基础咖啡
class BasicCoffee implements Coffee {
@Override
public double cost() {
return 5.0;
}
@Override
public String description() {
return "Basic Coffee";
}
}
// 装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public double cost() {
return coffee.cost();
}
@Override
public String description() {
return coffee.description();
}
}
// 具体装饰器:奶泡
class MilkFoam extends CoffeeDecorator {
public MilkFoam(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 2.0;
}
@Override
public String description() {
return coffee.description() + ", Milk Foam";
}
}
// 具体装饰器:糖浆
class Syrup extends CoffeeDecorator {
public Syrup(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return coffee.cost() + 1.0;
}
@Override
public String description() {
return coffee.description() + ", Syrup";
}
}
// 测试
public class DecoratorTest {
public static void main(String[] args) {
Coffee myCoffee = new BasicCoffee();
myCoffee = new MilkFoam(myCoffee);
myCoffee = new Syrup(myCoffee);
System.out.println(myCoffee.description()); // 输出: Basic Coffee, Milk Foam, Syrup
System.out.println(myCoffee.cost()); // 输出: 8.0
}
}
优点与缺点
- 优点:灵活扩展功能,遵循开闭原则。
- 缺点:可能导致过多的装饰器类,增加复杂性。
4. 代理模式(Proxy Pattern)
定义
代理模式是一种结构型设计模式,通过代理对象控制对原始对象的访问。代理可以在访问时添加额外的逻辑,如权限检查、缓存或延迟加载。
用途
- 控制访问权限(如用户认证)。
- 延迟加载大对象(虚拟代理)。
- 缓存请求结果。
示例场景
设想我们正在开发一个在线相册应用,用户可以浏览高清旅行照片。这些照片文件很大,加载一张照片可能需要从云端下载数兆字节的数据。如果用户只是快速浏览相册,预先加载所有高清图片会让应用变得缓慢且耗费流量。使用代理模式,我们可以创建一个“图片代理”,在用户点击查看某张照片时才真正加载高清版本。在此之前,代理可以显示一个低分辨率的占位图,减少加载时间,提升用户体验。
代码示例(Java)
// 图片接口
interface Image {
void display();
}
// 真实图片类
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImage();
}
private void loadImage() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理图片类
class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 测试
public class ProxyTest {
public static void main(String[] args) {
Image image = new ProxyImage("photo.jpg");
image.display(); // 输出: Loading image: photo.jpg
// Displaying image: photo.jpg
image.display(); // 输出: Displaying image: photo.jpg
}
}
优点与缺点
- 优点:控制访问,优化性能(如延迟加载)。
- 缺点:增加一层间接性,可能影响性能。
5. 工厂方法模式(Factory Method Pattern)
定义
工厂方法模式是一种创建型设计模式,定义一个用于创建对象的接口,但让子类决定实例化哪个类。它将对象的创建推迟到子类中。
用途
- 当需要创建的对象类型在运行时才能确定。
- 需要支持不同类型的对象创建,但希望保持代码的扩展性。
- 解耦对象的创建和使用。
示例场景
假设我们正在开发一个全球物流平台,用户可以选择不同的运输方式来寄送包裹,比如陆运的卡车、海运的货轮,或空运的飞机。每种运输方式有不同的速度、成本和适用场景。例如,卡车适合短途运输,货轮适合大宗货物,而飞机则用于紧急快递。使用工厂方法模式,我们可以为每种运输方式创建一个专门的工厂,动态决定创建哪种运输工具。这种设计让系统易于扩展,比如未来添加无人机运输时,只需新增一个工厂类,而无需修改现有代码。
代码示例(Java)
// 运输工具接口
interface Transport {
String deliver();
}
// 具体产品:卡车
class Truck implements Transport {
@Override
public String deliver() {
return "Delivering by truck";
}
}
// 具体产品:轮船
class Ship implements Transport {
@Override
public String deliver() {
return "Delivering by ship";
}
}
// 工厂接口
interface TransportFactory {
Transport createTransport();
}
// 具体工厂:卡车工厂
class TruckFactory implements TransportFactory {
@Override
public Transport createTransport() {
return new Truck();
}
}
// 具体工厂:轮船工厂
class ShipFactory implements TransportFactory {
@Override
public Transport createTransport() {
return new Ship();
}
}
// 测试
public class FactoryMethodTest {
public static void main(String[] args) {
TransportFactory truckFactory = new TruckFactory();
TransportFactory shipFactory = new ShipFactory();
System.out.println(truckFactory.createTransport().deliver()); // 输出: Delivering by truck
System.out.println(shipFactory.createTransport().deliver()); // 输出: Delivering by ship
}
}
优点与缺点
- 优点:支持扩展,符合开闭原则,解耦创建和使用逻辑。
- 缺点:可能增加类的数量,复杂化系统。
6. 观察者模式(Observer Pattern)
定义
观察者模式是一种行为型设计模式,定义了对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新。
用途
- 实现事件驱动系统,如 UI 事件监听。
- 需要在对象状态变化时通知多个对象。
- 解耦主体和观察者。
示例场景
假设我们正在开发一个实时新闻应用,类似于一个全球新闻推送平台。用户可以订阅不同的新闻类别,比如科技、体育或政治。当有重大新闻发生时,比如“科学家发现新型能源技术”,所有订阅了科技类新闻的用户都会收到推送通知。观察者模式让新闻平台(主体)能够动态管理订阅者列表,并在新闻发布时即时通知所有相关用户,确保他们不错过任何重要更新,同时保持系统的灵活性和可扩展性。
代码示例(Java)
import java.util.ArrayList;
import java.util.List;
// 观察者接口
interface Observer {
void update(String news);
}
// 主体(发布者)
class NewsAgency {
private List<Observer> subscribers = new ArrayList<>();
private String news;
public void subscribe(Observer observer) {
subscribers.add(observer);
}
public void unsubscribe(Observer observer) {
subscribers.remove(observer);
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : subscribers) {
observer.update(news);
}
}
}
// 具体观察者
class Subscriber implements Observer {
private String name;
public Subscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// 测试
public class ObserverTest {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
Subscriber sub1 = new Subscriber("Alice");
Subscriber sub2 = new Subscriber("Bob");
agency.subscribe(sub1);
agency.subscribe(sub2);
agency.setNews("Breaking: New design pattern discovered!");
// 输出:
// Alice received news: Breaking: New design pattern discovered!
// Bob received news: Breaking: New design pattern discovered!
agency.unsubscribe(sub1);
agency.setNews("Update: Pattern details released!");
// 输出:
// Bob received news: Update: Pattern details released!
}
}
优点与缺点
- 优点:支持松耦合,促进事件驱动开发。
- 缺点:可能导致内存泄漏(未正确取消订阅),通知过多可能影响性能。
7. 策略模式(Strategy Pattern)
定义
策略模式是一种行为型设计模式,定义一系列算法,将每个算法封装起来,使它们可以相互替换。它让算法的变化独立于使用算法的客户端。
用途
- 需要动态切换算法或行为。
- 避免使用大量条件语句来选择行为。
- 提高代码的可扩展性和可维护性。
示例场景
假设我们正在开发一个电商平台的支付系统,顾客在结账时可以选择多种支付方式:信用卡、PayPal、数字货币,甚至是货到付款。每种支付方式有不同的处理逻辑,比如信用卡需要验证卡号,PayPal需要跳转到第三方页面,而数字货币需要确认区块链交易。策略模式允许我们在运行时动态切换支付方式,让用户自由选择,同时保持支付流程的简洁和可扩展。未来如果新增支付方式,比如微信支付,只需添加一个新策略类即可。
代码示例(Java)
// 策略接口
interface PaymentStrategy {
String pay(double amount);
}
// 具体策略:信用卡支付
class CreditCardPayment implements PaymentStrategy {
@Override
public String pay(double amount) {
return "Paid " + amount + " using Credit Card";
}
}
// 具体策略:PayPal支付
class PayPalPayment implements PaymentStrategy {
@Override
public String pay(double amount) {
return "Paid " + amount + " using PayPal";
}
}
// 上下文
class ShoppingCart {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public String checkout(double amount) {
return strategy.pay(amount);
}
}
// 测试
public class StrategyTest {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
System.out.println(cart.checkout(100)); // 输出: Paid 100.0 using Credit Card
cart.setPaymentStrategy(new PayPalPayment());
System.out.println(cart.checkout(100)); // 输出: Paid 100.0 using PayPal
}
}
优点与缺点
- 优点:支持动态切换行为,符合开闭原则,减少条件语句。
- 缺点:客户端需要了解所有策略,增加类数量。
8. 适配器模式(Adapter Pattern)
定义
适配器模式是一种结构型设计模式,允许将不兼容的接口转换为客户端期望的接口。它通过包装不兼容的对象来实现接口的适配。
用途
- 集成遗留系统或第三方库时,接口不兼容。
- 统一不同系统的接口。
- 重用现有类但需要适配新接口。
示例场景
假设我们是一家大型企业的IT部门负责人,公司的库存管理系统依赖一个老旧的遗留系统,使用过时的日志记录接口,只能记录简单的文本消息。而新开发的监控系统要求日志带有级别(比如INFO、ERROR)和时间戳。但是我们不想重写整个遗留系统,因为它稳定且核心业务依赖它。适配器模式在这时就可以派上用场:我们可以创建一个适配器,将新系统的日志接口转换为旧系统的接口,让遗留系统无缝集成到新监控系统中,既节省了开发成本,又满足了新需求。
代码示例(Java)
// 旧日志接口
interface OldLogger {
void logMessage(String message);
}
// 旧日志系统
class LegacyLogger implements OldLogger {
@Override
public void logMessage(String message) {
System.out.println("Old Logger: " + message);
}
}
// 新日志接口
interface NewLogger {
void log(String level, String message);
}
// 新日志系统
class ModernLogger implements NewLogger {
@Override
public void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
}
// 适配器
class LoggerAdapter implements NewLogger {
private OldLogger oldLogger;
public LoggerAdapter(OldLogger oldLogger) {
this.oldLogger = oldLogger;
}
@Override
public void log(String level, String message) {
oldLogger.logMessage(level + ": " + message);
}
}
// 测试
public class AdapterTest {
public static void main(String[] args) {
OldLogger oldLogger = new LegacyLogger();
NewLogger adapter = new LoggerAdapter(oldLogger);
adapter.log("INFO", "System started"); // 输出: Old Logger: INFO: System started
NewLogger newLogger = new ModernLogger();
newLogger.log("INFO", "System started"); // 输出: [INFO] System started
}
}
优点与缺点
- 优点:提高代码复用性,适配不兼容接口。
- 缺点:可能增加系统复杂性,适配器类可能难以维护。
总结
设计模式是软件开发中的强大工具,帮助我们以优雅的方式解决常见问题。以下是八种模式的快速对比:
模式 | 类型 | 核心思想 | 典型场景 |
---|---|---|---|
原型模式 | 创建型 | 通过克隆创建对象 | 复制复杂对象(如游戏角色) |
单例模式 | 创建型 | 确保单一实例 | 全局配置管理(如智能家居控制面板) |
装饰器模式 | 结构型 | 动态扩展功能 | 功能组合(如咖啡定制) |
代理模式 | 结构型 | 控制对象访问 | 延迟加载、权限控制(如图片加载) |
工厂方法模式 | 创建型 | 推迟对象创建到子类 | 动态创建不同类型对象(如物流运输) |
观察者模式 | 行为型 | 一对多状态通知 | 事件驱动系统(如新闻推送) |
策略模式 | 行为型 | 动态切换算法 | 支付方式选择(如电商支付) |
适配器模式 | 结构型 | 接口适配 | 集成遗留系统(如日志系统) |