策略模式:用旅行方式的选择理解算法替换

61 阅读7分钟

一、故事解说:小明如何选择出行方式

假设小明要去上班,有多种出行方式:

  1. 天气晴朗:选择骑自行车(省钱、环保);

  2. 下雨:选择打车(快,不淋雨);

  3. 时间充裕:选择坐地铁(便宜,但慢)。

策略模式核心:定义一组算法(如出行方式),将每个算法封装起来(如自行车类、出租车类),使它们可以相互替换。客户端(小明)可以在运行时动态选择不同的算法。

二、策略模式核心结构(出行方式案例)

java

// 1. 策略接口:出行方式
interface TravelStrategy {
    void travel(); // 出行方法
}

// 2. 具体策略:自行车
class BikeStrategy implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("骑自行车上班,省钱环保");
    }
}

// 3. 具体策略:出租车
class TaxiStrategy implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("打出租车上班,快速舒适");
    }
}

// 4. 具体策略:地铁
class SubwayStrategy implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("坐地铁上班,便宜但拥挤");
    }
}

// 5. 上下文:小明
class XiaoMing {
    private TravelStrategy strategy;
    
    public void setStrategy(TravelStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void goToWork() {
        if (strategy == null) {
            System.out.println("请先选择出行方式");
            return;
        }
        strategy.travel();
    }
}

// 6. 客户端:动态选择策略
public class Client {
    public static void main(String[] args) {
        XiaoMing xiaoming = new XiaoMing();
        
        // 晴天:骑自行车
        xiaoming.setStrategy(new BikeStrategy());
        xiaoming.goToWork(); // 输出:骑自行车上班,省钱环保
        
        // 下雨:打出租车
        xiaoming.setStrategy(new TaxiStrategy());
        xiaoming.goToWork(); // 输出:打出租车上班,快速舒适
    }
}

三、Android 常用策略模式案例与实现

案例 1:图片加载策略(Glide/Picasso 选择)

java

// 1. 策略接口:图片加载器
interface ImageLoader {
    void loadImage(String url, ImageView imageView);
}

// 2. 具体策略:Glide加载器
class GlideLoader implements ImageLoader {
    @Override
    public void loadImage(String url, ImageView imageView) {
        Glide.with(imageView.getContext())
            .load(url)
            .into(imageView);
        System.out.println("使用Glide加载图片:" + url);
    }
}

// 3. 具体策略:Picasso加载器
class PicassoLoader implements ImageLoader {
    @Override
    public void loadImage(String url, ImageView imageView) {
        Picasso.get()
            .load(url)
            .into(imageView);
        System.out.println("使用Picasso加载图片:" + url);
    }
}

// 4. 上下文:图片加载管理器
class ImageLoaderManager {
    private ImageLoader loader;
    
    public ImageLoaderManager(ImageLoader loader) {
        this.loader = loader;
    }
    
    public void setLoader(ImageLoader loader) {
        this.loader = loader;
    }
    
    public void load(String url, ImageView imageView) {
        if (loader == null) {
            throw new IllegalStateException("请先设置图片加载器");
        }
        loader.loadImage(url, imageView);
    }
}

// 5. 在Activity中使用
public class MainActivity extends AppCompatActivity {
    private ImageView imageView;
    private ImageLoaderManager manager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        
        // 初始化管理器,默认使用Glide
        manager = new ImageLoaderManager(new GlideLoader());
        
        // 加载图片
        manager.load("https://example.com/image.jpg", imageView);
        
        // 动态切换到Picasso
        manager.setLoader(new PicassoLoader());
        manager.load("https://example.com/another_image.jpg", imageView);
    }
}

优点

  • 可替换性:可在运行时动态切换图片加载库(如从 Glide 切换到 Picasso);

  • 解耦实现:图片加载逻辑与 Activity 分离,代码更清晰;

  • 扩展性好:新增图片加载库(如 Fresco)只需实现 ImageLoader 接口,无需修改现有代码。

缺点

  • 类数量增加:每个策略需要一个类,若策略过多会导致类膨胀;
  • 客户端需了解策略:Activity 需要知道有哪些策略(Glide/Picasso),违反迪米特法则。

案例 2:加密策略(MD5/SHA-256 选择)

java

// 1. 策略接口:加密算法
interface EncryptionStrategy {
    String encrypt(String data);
}

// 2. 具体策略:MD5加密
class MD5Strategy implements EncryptionStrategy {
    @Override
    public String encrypt(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(data.getBytes());
            return bytesToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return data;
        }
    }
    
    private String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
}

// 3. 具体策略:SHA-256加密
class SHA256Strategy implements EncryptionStrategy {
    @Override
    public String encrypt(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(data.getBytes());
            return bytesToHex(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return data;
        }
    }
    
    private String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
}

// 4. 上下文:加密管理器
class EncryptionManager {
    private EncryptionStrategy strategy;
    
    public EncryptionManager(EncryptionStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(EncryptionStrategy strategy) {
        this.strategy = strategy;
    }
    
    public String encryptData(String data) {
        return strategy.encrypt(data);
    }
}

// 5. 在Activity中使用
public class LoginActivity extends AppCompatActivity {
    private EditText passwordEditText;
    private EncryptionManager manager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        passwordEditText = findViewById(R.id.password);
        
        // 默认使用SHA-256加密
        manager = new EncryptionManager(new SHA256Strategy());
    }
    
    public void onLoginClick(View view) {
        String password = passwordEditText.getText().toString();
        // 加密密码
        String encryptedPassword = manager.encryptData(password);
        // 发送到服务器
        sendToServer(encryptedPassword);
    }
    
    // 切换加密策略(如用户选择更安全的加密方式)
    public void switchToMD5(View view) {
        manager.setStrategy(new MD5Strategy());
        Toast.makeText(this, "已切换到MD5加密", Toast.LENGTH_SHORT).show();
    }
}

优点

  • 算法独立:不同加密算法(MD5/SHA-256)相互独立,便于维护和测试;

  • 运行时切换:可根据用户需求或安全级别动态切换加密算法;

  • 符合开闭原则:新增加密算法(如 AES)只需实现 EncryptionStrategy 接口,无需修改现有代码。

缺点

  • 策略选择逻辑复杂:若加密算法选择逻辑复杂(如根据数据大小、类型选择),会增加客户端代码复杂度;
  • 策略创建成本:每次创建新策略对象(如 new SHA256Strategy ())可能有性能开销。

案例 3:缓存策略(内存缓存 / 磁盘缓存)

java

// 1. 策略接口:缓存
interface CacheStrategy {
    void put(String key, Object value);
    Object get(String key);
    void clear();
}

// 2. 具体策略:内存缓存(使用LruCache)
class MemoryCacheStrategy implements CacheStrategy {
    private LruCache<String, Object> cache;
    
    public MemoryCacheStrategy() {
        // 获取最大可用内存的1/8作为缓存大小
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        cache = new LruCache<String, Object>(cacheSize) {
            @Override
            protected int sizeOf(String key, Object value) {
                return 1; // 简单实现,实际应计算对象大小
            }
        };
    }
    
    @Override
    public void put(String key, Object value) {
        cache.put(key, value);
        System.out.println("内存缓存:存入" + key);
    }
    
    @Override
    public Object get(String key) {
        Object value = cache.get(key);
        System.out.println("内存缓存:获取" + key + (value != null ? "成功" : "失败"));
        return value;
    }
    
    @Override
    public void clear() {
        cache.evictAll();
        System.out.println("内存缓存:已清空");
    }
}

// 3. 具体策略:磁盘缓存(简化示例)
class DiskCacheStrategy implements CacheStrategy {
    private Context context;
    
    public DiskCacheStrategy(Context context) {
        this.context = context;
    }
    
    @Override
    public void put(String key, Object value) {
        // 实际中应将数据写入文件
        System.out.println("磁盘缓存:存入" + key);
    }
    
    @Override
    public Object get(String key) {
        // 实际中应从文件读取数据
        System.out.println("磁盘缓存:获取" + key);
        return null; // 简化示例
    }
    
    @Override
    public void clear() {
        // 实际中应删除所有缓存文件
        System.out.println("磁盘缓存:已清空");
    }
}

// 4. 上下文:缓存管理器
class CacheManager {
    private CacheStrategy strategy;
    
    public CacheManager(CacheStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(CacheStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void cacheData(String key, Object value) {
        strategy.put(key, value);
    }
    
    public Object retrieveData(String key) {
        return strategy.get(key);
    }
    
    public void clearCache() {
        strategy.clear();
    }
}

// 5. 在Activity中使用
public class MainActivity extends AppCompatActivity {
    private CacheManager manager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 默认使用内存缓存
        manager = new CacheManager(new MemoryCacheStrategy());
        
        // 缓存数据
        manager.cacheData("user_info", new User("张三", 25));
        
        // 读取数据
        User user = (User) manager.retrieveData("user_info");
        
        // 切换到磁盘缓存
        manager.setStrategy(new DiskCacheStrategy(this));
        manager.cacheData("config", new Config());
    }
}

优点

  • 按需选择缓存:根据数据重要性和使用频率选择内存缓存或磁盘缓存;

  • 解耦缓存逻辑:Activity 无需关心缓存实现细节,只通过 CacheManager 操作;

  • 扩展性好:新增缓存策略(如双缓存、网络缓存)只需实现 CacheStrategy 接口。

缺点

  • 缓存同步问题:切换缓存策略后,可能需要处理数据同步问题(如内存缓存和磁盘缓存不一致);
  • 策略配置复杂:若有多种缓存策略组合(如先查内存再查磁盘),会增加配置复杂度。

四、策略模式的适用场景与总结

适用场景

  1. 多种算法可选:如图片加载、加密、缓存、排序等场景,有多种实现方式;
  2. 运行时动态切换:需要根据条件(如用户偏好、网络状态)动态选择算法;
  3. 避免多重条件判断:替代大量if-elseswitch语句,使代码更清晰;
  4. 算法独立变化:算法的实现细节经常变化,使用策略模式可独立维护。

核心优点

  • 算法解耦:将不同算法封装到独立策略类中,提高可维护性;
  • 可替换性:可在运行时动态切换算法,灵活性高;
  • 符合开闭原则:新增策略无需修改现有代码,只需实现接口;
  • 避免条件语句:替代复杂的条件判断,使代码更简洁。

核心缺点

  • 类数量增加:每个策略需一个类,导致类膨胀;
  • 策略暴露:客户端需要知道有哪些策略,增加使用难度;
  • 策略创建成本:频繁创建策略对象可能影响性能(可通过策略工厂 + 享元模式优化)。

Android 中的最佳实践

  • 利用系统策略:如 Android 的动画插值器(AccelerateInterpolator、DecelerateInterpolator)就是策略模式的应用;

  • 自定义策略时

    1. 使用枚举或工厂类管理策略,避免客户端直接依赖具体策略类;

    2. 对策略进行分组(如按功能、性能),减少客户端选择复杂度;

    3. 结合单例模式或享元模式复用策略对象,降低创建成本。

策略模式是 Android 开发中最常用的设计模式之一,理解它有助于写出更灵活、可维护的代码。