编程的艺术:解析六大设计模式,助你写出更优雅的代码

1. 观察者模式(Observer Pattern)

  • 应用场景解释: 当一个对象的状态发生变化时,希望所有依赖于它的对象都能收到通知并自动更新。可以想象成报社和订阅者的关系报社是发布者,订阅者是观察者。当报社有新的新闻(状态变化)时,订阅者会收到报纸并了解最新情况。

  • 场景: 假设你正在构建一个简单的天气应用程序,你希望当天气状态发生变化时,能够及时通知多个观察者(例如显示当前温度的窗口、显示天气图标的窗口等)。

    • 实例: 首先创建一个接口Observer定义观察者应该实现的方法,通常是在发布者状态发生变化时调用。
    public interface Observer {
        // 观察者执行逻辑
        void update(float temperature);
        // 其他...
    }
    
    • 定义主题接口Subject,定义了主题(天气数据)的基本行为,包括注册、移除和通知观察者。
    public interface Subject {
        void registerObserver(Observer observer);
        void removeObserver(Observer observer);
        void notifyObservers();
    }
    
    • 创建一个WeatherData类作为主题,并实现主题接口Subject,实现注册、移除和通知观察者的方法。
    import java.util.ArrayList;
    import java.util.List;
    
    public class WeatherData implements Subject {
        // 存放观察者的列表
        private List<Observer> observers;
        private float temperature;
    
        public WeatherData() {
            observers = new ArrayList<>();
        }
    
        // 添加观察者,即订阅天气消息
        @Override
        public void registerObserver(Observer observer) {
            observers.add(observer);
        }
    
        // 移除观察者,即取消订阅
        @Override
        public void removeObserver(Observer observer) {
            observers.remove(observer);
        }
        
        // 通知观察者
        @Override
        public void notifyObservers() {
            for (Observer observer : observers) {
                observer.update(temperature);
            }
        }
    
        // 模拟天气数据变化,并在变化时通知观察者
        public void setTemperature(float temperature) {
            this.temperature = temperature;
            notifyObservers();
        }
    }
    
    • 创建具体观察者类TemperatureDisplayWeatherIconDisplay,并实现主题接口Observer,实现:在发布者(天气)状态发生变化时观察者真正的执行逻辑。
    public class TemperatureDisplay implements Observer {
        private float temperature;
    
        @Override
        public void update(float temperature) {
            this.temperature = temperature;
            display();
        }
    
        public void display() {
            System.out.println("Temperature Display: " + temperature + " degrees Celsius");
        }
    }
    
    public class WeatherIconDisplay implements Observer {
        private float temperature;
    
        @Override
        public void update(float temperature) {
            this.temperature = temperature;
            display();
        }
    
        public void display() {
            System.out.println("Weather Icon Display: " + getWeatherIcon());
        }
    
        private String getWeatherIcon() {
            // 根据温度等信息确定天气图标
            // 简化为示例,实际应根据具体逻辑处理
            if (temperature > 25) {
                return "☀️";
            } else {
                return "❄️";
            }
        }
    }
    
    • 使用观察者模式
    public class Main {
        public static void main(String[] args) {
            WeatherData weatherData = new WeatherData();
    
            TemperatureDisplay temperatureDisplay = new TemperatureDisplay();
            WeatherIconDisplay weatherIconDisplay = new WeatherIconDisplay();
    
            // 添加观察者/订阅
            weatherData.registerObserver(temperatureDisplay);
            weatherData.registerObserver(weatherIconDisplay);
    
            // 模拟天气数据变化
            weatherData.setTemperature(30.0f);
            // 输出:
            // Temperature Display: 30.0 degrees Celsius
            // Weather Icon Display: ☀️
    
            weatherData.setTemperature(20.0f);
            // 输出:
            // Temperature Display: 20.0 degrees Celsius
            // Weather Icon Display: ❄️
        }
    }
    

    WeatherData充当主题,而TemperatureDisplayWeatherIconDisplay是具体的观察者。通过注册和通知的方式,主题能够实时通知所有观察者,使它们能够根据天气数据的变化更新自己的显示。

2. 装饰者模式(Decorator Pattern)

  • 应用场景解释:当我们想要在不修改原始类代码的情况下,动态地给一个对象添加一些额外的职责。想象你有一个咖啡店,你有一种基本的咖啡(ConcreteComponent),然后你可以用装饰器(比如加糖、加奶)来动态地添加额外的功能,而不改变原始咖啡对象。

  • 场景: 假设你经营一家咖啡店,有不同种类的咖啡(例如浓缩咖啡、拿铁、美式咖啡等),以及一些额外的配料选项(例如牛奶、糖浆、摩卡等)。你希望能够在不修改每个具体咖啡类的情况下,动态地为咖啡添加不同的配料。

    • 实例: 创建一个Coffee接口,定义了制作咖啡的方法。
    public interface Coffee {
        void makeCoffee();
    }
    
    • 然后,实现不同种类的具体咖啡类EspressoLatte
    public class Espresso implements Coffee {
        @Override
        public void makeCoffee() {
            System.out.println("Making Espresso");
        }
    }
    
    public class Latte implements Coffee {
        @Override
        public void makeCoffee() {
            System.out.println("Making Latte");
        }
    }
    
    • 接下来,创建一个CoffeeDecorator抽象类,继承自Coffee接口,用于装饰具体的咖啡类。
    public abstract class CoffeeDecorator implements Coffee {
        protected Coffee decoratedCoffee;
    
        public CoffeeDecorator(Coffee decoratedCoffee) {
            this.decoratedCoffee = decoratedCoffee;
        }
    
        @Override
        public void makeCoffee() {
            decoratedCoffee.makeCoffee();
        }
    }
    
    • 最后,实现具体的装饰器类,如MilkDecoratorMochaDecorator,它们分别添加牛奶和摩卡。
    public class MilkDecorator extends CoffeeDecorator {
        public MilkDecorator(Coffee decoratedCoffee) {
            super(decoratedCoffee);
        }
    
        @Override
        public void makeCoffee() {
            super.makeCoffee();
            addMilk();
        }
    
        private void addMilk() {
            System.out.println("Adding Milk");
        }
    }
    
    public class MochaDecorator extends CoffeeDecorator {
        public MochaDecorator(Coffee decoratedCoffee) {
            super(decoratedCoffee);
        }
    
        @Override
        public void makeCoffee() {
            super.makeCoffee();
            addMocha();
        }
    
        private void addMocha() {
            System.out.println("Adding Mocha");
        }
    }
    
    • 使用装饰器模式
    public class Main {
        public static void main(String[] args) {
            Coffee espresso = new Espresso();
            espresso.makeCoffee();
    
            Coffee latteWithMilk = new MilkDecorator(new Latte());
            latteWithMilk.makeCoffee();
    
            Coffee mochaLatte = new MochaDecorator(new MilkDecorator(new Latte()));
            mochaLatte.makeCoffee();
        }
    }
    

    这个例子演示了如何使用装饰器模式,在不修改具体咖啡类的情况下,动态地为咖啡添加不同的配料。

3. 策略模式(Strategy Pattern)

  • 应用场景:封装有多种算法或行为,希望在运行时能够灵活地选择其中之一。想象一个电商网站,可以根据不同的策略来计算折扣,比如春节特惠、会员折扣等,这些都可以作为不同的策略。

  • 场景: 假设你正在设计一个商场的促销系统,不同的季节或活动需要使用不同的促销策略,例如打折、满减或赠送礼品。

    • 实例: 首先创建一个PromotionStrategy接口,定义了促销策略的抽象方法applyDiscount
    public interface PromotionStrategy {
        void applyDiscount(double amount);
    }
    
    • 然后,实现不同的促销策略类,如DiscountPromotionGiftPromotion,它们分别实现了PromotionStrategy接口。
    public class DiscountPromotion implements PromotionStrategy {
        @Override
        public void applyDiscount(double amount) {
            // 实现打折逻辑
            // ...
        }
    }
    
    public class GiftPromotion implements PromotionStrategy {
        @Override
        public void applyDiscount(double amount) {
            // 实现赠送礼品逻辑
            // ...
        }
    }
    
    • 最后,创建一个PromotionContext类,其中包含一个PromotionStrategy成员变量,并在需要的时候切换不同的促销策略。
    public class PromotionContext {
        private PromotionStrategy promotionStrategy;
    
        public PromotionContext(PromotionStrategy promotionStrategy) {
            this.promotionStrategy = promotionStrategy;
        }
    
        public void setPromotionStrategy(PromotionStrategy promotionStrategy) {
            this.promotionStrategy = promotionStrategy;
        }
    
        public void applyDiscount(double amount) {
            promotionStrategy.applyDiscount(amount);
        }
    }
    
    • 使用策略模式
    public class Main {
        public static void main(String[] args) {
            PromotionContext context = new PromotionContext(new DiscountPromotion());
            context.applyDiscount(100.0);
    
            context.setPromotionStrategy(new GiftPromotion());
            context.applyDiscount(200.0);
        }
    }
    

    这个例子通过在运行时切换不同的促销策略,灵活地应对不同的商场促销需求。

4. 工厂模式(Factory Pattern)

  • 应用场景解释: 提供了一种创建对象的接口,但允许子类决定实例化哪个类;这样,一个类的实例化过程延迟到其子类。想象你正在创建一个简单的电子设备工厂,可以生产不同类型的电子设备,如手机、平板电脑和笔记本电脑。

  • 场景: 假设你经营一家电子设备工厂,你生产手机和平板电脑两种不同类型的设备。你希望能够通过一个共同的接口来创建这两种设备,并且在未来可能添加更多类型的设备。

    • 实例: 创建一个Device接口。
    public interface Device {
        void turnOn();
        void turnOff();
    }
    
    • 然后实现不同类型的设备类PhoneTablet
    public class Phone implements Device {
        @Override
        public void turnOn() {
            System.out.println("Phone is turning on");
        }
    
        @Override
        public void turnOff() {
            System.out.println("Phone is turning off");
        }
    }
    
    public class Tablet implements Device {
        @Override
        public void turnOn() {
            System.out.println("Tablet is turning on");
        }
    
        @Override
        public void turnOff() {
            System.out.println("Tablet is turning off");
        }
    }
    
    • 接着,实现一个工厂接口DeviceFactory,具有创建不同设备的方法。
    public interface DeviceFactory {
        Device createDevice();
    }
    
    • 最后,创建具体的工厂类,如PhoneFactoryTabletFactory,用于生产相应的设备。
    public class PhoneFactory implements DeviceFactory {
        @Override
        public Device createDevice() {
            return new Phone();
        }
    }
    
    public class TabletFactory implements DeviceFactory {
        @Override
        public Device createDevice() {
            return new Tablet();
        }
    }
    
    • 使用工厂模式
    public class Main {
        public static void main(String[] args) {
            DeviceFactory phoneFactory = new PhoneFactory();
            Device phone = phoneFactory.createDevice();
            phone.turnOn();
            phone.turnOff();
    
            DeviceFactory tabletFactory = new TabletFactory();
            Device tablet = tabletFactory.createDevice();
            tablet.turnOn();
            tablet.turnOff();
        }
    }
    

    这个例子展示了如何使用工厂模式,通过一个共同的接口来创建不同类型的设备,以及如何在未来扩展工厂以支持更多类型的设备。

5. 适配器模式(Adapter Pattern)

  • 应用场景解释: 想象有一个用于播放音频的类,它有一个play方法,但是有另外一个类,它的方法是start。为了使这两个类能够一起工作,可以创建一个适配器类,实现目标接口,内部持有被适配者的实例,并在目标接口的方法中调用被适配者的方法。

  • 场景: 假设你有一个旧的音频播放器只能播放mp3格式的音频文件,但是你希望能够播放其他格式,比如mp4aac。我们可以创建一个适配器,使得旧的音频播放器能够兼容新的音频文件格式。

    • 实例: 创建一个AudioPlayer接口,定义了播放音频文件的方法。
    public interface AudioPlayer {
        void play(String audioType, String fileName);
    }
    
    • 然后,实现具体的旧音频播放器类OldAudioPlayer,它只能播放mp3格式的音频。
    public class OldAudioPlayer implements AudioPlayer {
        @Override
        public void play(String audioType, String fileName) {
            if (audioType.equalsIgnoreCase("mp3")) {
                System.out.println("Playing mp3 file: " + fileName);
            } else {
                System.out.println("Unsupported audio type: " + audioType);
            }
        }
    }
    
    • 接着,创建一个新的音频播放器接口AdvancedAudioPlayer,定义了播放其他格式音频的方法。
    public interface AdvancedAudioPlayer {
        void playMp4(String fileName);
        void playAac(String fileName);
    }
    
    public class NewAudioPlayer implements AdvancedAudioPlayer {
        @Override
        public void playMp4(String fileName) {
            System.out.println("Playing mp4 file: " + fileName);
        }
    
        @Override
        public void playAac(String fileName) {
            System.out.println("Playing aac file: " + fileName);
        }
    }
    
    • 最后,创建一个适配器Adapter,实现了旧的音频播放器接口,并在内部使用新的音频播放器,使其能够播放其他格式的音频。
    public class Adapter implements AudioPlayer {
        private AdvancedAudioPlayer advancedAudioPlayer;
    
        public Adapter(AdvancedAudioPlayer advancedAudioPlayer) {
            this.advancedAudioPlayer = advancedAudioPlayer;
        }
    
        @Override
        public void play(String audioType, String fileName) {
            if (audioType.equalsIgnoreCase("mp4")) {
                advancedAudioPlayer.playMp4(fileName);
            } else if (audioType.equalsIgnoreCase("aac")) {
                advancedAudioPlayer.playAac(fileName);
            } else if (audioType.equalsIgnoreCase("mp3")){
                new OldAudioPlayer().playAac(fileName)
            } else{
                System.out.println("Unsupported audio type: " + audioType);
            }
        }
    }
    
    • 使用适配器模式
    public class Main {
        public static void main(String[] args) {
            AudioPlayer audioPlayer = new OldAudioPlayer();
            audioPlayer.play("mp3", "song.mp3");
    
            AdvancedAudioPlayer newAudioPlayer = new NewAudioPlayer();
            AudioPlayer adapter = new Adapter(newAudioPlayer);
            adapter.play("mp4", "movie.mp4");
            adapter.play("aac", "audio.aac");
            audioPlayer.play("mp3", "song.mp3");
        }
    }
    

    这个例子演示了如何使用适配器模式,通过适配器将旧的音频播放器与新的音频播放器接口兼容,从而能够播放不同格式的音频文件。

6. 单例模式(Singleton Pattern)

  • 应用场景解释: 单例模式通常用于需要全局共享访问点的场景,例如配置管理、日志管理、数据库连接池等。单例模式可以有多种实现方式,其中最常见的是懒汉模式饿汉模式

    • 懒汉模式: 在第一次请求实例时进行实例化。线程安全需要考虑,可以通过双重检查锁定等方式实现。
    • 饿汉模式: 在类加载时就进行实例化。线程安全,但可能在程序启动时就创建实例,可能会影响性能。
  • 场景: 假设你正在设计一个日志记录器,你希望在整个应用程序中只有一个日志记录器实例,以确保日志信息的一致性。

    • 实例: 创建一个ConfigManager类,使用单例模式确保只有一个实例存在。该类可以包含方法来读取和写入配置信息。
    public class ConfigManager {
        private static ConfigManager instance;
        private String configData;
    
        private ConfigManager() {
            // 私有构造函数,防止外部直接实例化
            // 初始化配置数据等操作
            this.configData = "DefaultConfigData";
        }
    
        public static ConfigManager getInstance() {
            if (instance == null) {
                instance = new ConfigManager();
            }
            return instance;
        }
    
        public String getConfigData() {
            return configData;
        }
    
        public void setConfigData(String configData) {
            this.configData = configData;
        }
    }
    
    • 使用单例模式
    public class Main {
        public static void main(String[] args) {
            ConfigManager configManager = ConfigManager.getInstance();
            System.out.println("Initial Config Data: " + configManager.getConfigData());
    
            // 修改配置数据
            configManager.setConfigData("NewConfigData");
    
            // 获取修改后的配置数据
            ConfigManager updatedConfigManager = ConfigManager.getInstance();
            System.out.println("Updated Config Data: " + updatedConfigManager.getC
    

    最后再提一嘴:线程安全的单例模式确保在多线程环境下只创建一个实例,并提供对该实例的全局访问点。以下是一种线程安全的单例模式实现,使用了双重检查锁定来确保在多线程环境下的性能和正确性。

    public class ThreadSafeSingleton {
    
        // 使用volatile关键字确保多线程环境下的可见性
        private static volatile ThreadSafeSingleton instance;
    
        // 私有构造函数,防止外部直接实例化
        private ThreadSafeSingleton() {
            // 初始化操作
        }
    
        // 全局访问点,双重检查锁定保证线程安全
        public static ThreadSafeSingleton getInstance() {
            if (instance == null) {
                synchronized (ThreadSafeSingleton.class) {
                    if (instance == null) {
                        instance = new ThreadSafeSingleton();
                    }
                }
            }
            return instance;
        }
    
        // 其他方法
    }
    

    volatile关键字确保了在多线程环境下对instance的可见性。在getInstance方法中,首先检查instance是否已经被实例化,如果没有,则进入同步块。在同步块中再次检查instance是否为null,如果是,则创建实例。这样,通过双重检查锁定,可以在多线程环境中保证只有一个实例被创建。

7. 参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值