策略模式初窥
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响到使用算法的客户端。在软件开发中,策略模式就像是一个万能的 “策略工具箱”,当我们遇到一个问题有多种解决方案,并且需要在不同的场景下灵活切换这些方案时,策略模式就能派上用场。
举个生活中的例子,我们日常出行,可以选择步行、骑自行车、坐公交车或者打车。这些出行方式就相当于不同的策略,而我们根据当天的时间、天气、目的地等因素来选择合适的出行方式,这就是策略模式的应用。在软件世界里,比如电商系统中的支付模块,支持微信支付、支付宝支付、银行卡支付等多种支付方式,用户可以根据自己的喜好和实际情况选择支付方式,这背后就是策略模式在支撑。
策略模式的出现,主要是为了解决代码的可维护性和扩展性问题。当我们的代码中存在大量的条件判断语句(如 if - else 或 switch - case)来处理不同的业务逻辑时,代码会变得臃肿、难以维护,而且每增加一种新的业务逻辑,都需要修改这些条件判断语句,这不符合软件开发中的开闭原则(对扩展开放,对修改关闭)。而策略模式通过将不同的业务逻辑封装成独立的策略类,使得代码更加清晰、易于维护和扩展。例如,在上述电商系统支付模块中,如果后续要增加新的支付方式,只需要新增一个支付策略类,实现支付接口,而不需要修改原有的支付逻辑代码。
鸿蒙与 ArkTS 基础
鸿蒙开发环境搭建
在开启鸿蒙应用开发之旅前,我们首先要搭建好开发环境。鸿蒙开发的主要工具是 DevEco Studio,它是华为官方推出的一站式开发平台,为开发者提供了代码编辑、编译构建、调试等全方位的功能支持,就像一个功能齐全的 “开发工厂”,助力我们高效地打造鸿蒙应用。以下是 DevEco Studio 的安装和配置步骤:
- 下载:访问华为开发者官网(https://developer.harmonyos.com/cn/develop/deveco-studio/ ),根据你的操作系统(Windows 或 Mac)下载对应的 DevEco Studio 安装包。下载完成后,你就拿到了开启鸿蒙开发大门的 “钥匙坯子”,接下来要 “打磨成型”。
- 安装:
-
- Windows 系统:双击下载的安装文件(.exe 格式),在安装向导中,你可以选择安装路径,默认是安装在 C:\Program Files\Huawei\DevEco Studio ,你也可以根据自己的磁盘空间和使用习惯,通过点击 “Browse…” 按钮指定其他安装路径,之后点击 “Next”。在安装选项界面,勾选 DevEco Studio 后,继续点击 “Next”,直到安装完成,最后点击 “Finish”。
-
- Mac 系统:将下载的安装包解压,把 DevEco Studio 应用程序拖动到 “Applications” 文件夹中即可完成安装,就像把一件物品放置到对应的收纳箱里。
- 配置环境:
-
- 双击打开 DevEco Studio,首次启动时,会进入配置向导。选择 “Agree”,同意相关条款,进入配置页面。这里就像是进入了一个工具的 “设置中心”,要进行一系列基础设置。
-
- 进行基础配置,包括设置 Node.js 与 Ohpm 的安装路径。Node.js 版本要求为 v14.19.1 及以上,且低于 v17.0.0;对应的 npm 版本要求为 6.14.16 及以上。如果本地没有合适的版本,可以点击 “Install” 按钮,选择下载源和存储路径后,进行在线下载。这一步就像是为工具配备合适的 “零部件”。
-
- 点击 “Next” 进入 SDK 配置,设置 SDK 的存储路径,然后点击 “Next”,会显示 “SDK License Agreement”,阅读相关协议后,勾选 “Accept”,同意协议才能继续使用 SDK 这个强大的 “工具包”。
-
- 单击 “Next” 进入配置预览页,确认各项配置无误后,再次单击 “Next”,等待配置自动下载完成,完成后单击 “Finish”,此时 DevEco Studio 会进入欢迎页,这就标志着开发环境成功配置好了,就像所有的准备工作就绪,可以正式开工了。
ArkTS 语言特性
ArkTS 作为鸿蒙原生应用开发的主力语言,在 TypeScript 的基础上进行了强化和扩展,为鸿蒙应用开发带来了诸多便利和强大的能力,就像为开发者配备了一套多功能的 “超级装备”。其关键特性如下:
- 类型系统:
-
- 静态类型检查:ArkTS 在编译阶段就会对代码进行类型检查,这能提前发现许多潜在的类型错误,比如将一个字符串类型的数据赋值给一个数字类型的变量,在编译时就能被检测出来,避免在运行时出现难以调试的错误,大大提高了代码的稳定性和可靠性,就像在生产线上提前检测出不合格的产品。
-
- 类型推断:它具备强大的类型推断能力,在很多情况下,开发者无需显式声明变量的类型,编译器可以根据变量的初始值自动推断其类型 。例如let num = 10;,编译器会自动推断num为数字类型,这在一定程度上减少了代码的冗余,让代码更加简洁易读,提高了开发效率,就像智能助手自动帮你完成一些重复性的标注工作。
- 装饰器:
-
- @Component 装饰器:用于标记一个结构体为可复用的 UI 组件,它是构建鸿蒙应用界面的基本单元。通过这个装饰器,开发者可以将一些 UI 元素和相关逻辑封装在一起,方便在不同的地方复用。例如:
@Component
export struct MyButton {
build() {
Button('点击我').onClick(() => {
console.log('按钮被点击了');
});
}
}
在其他组件中,就可以像使用普通组件一样使用MyButton,实现了代码的模块化和可维护性,就像使用预制的建筑模块来搭建房屋。
- @Entry 装饰器:被@Entry装饰的组件用作页面的默认入口组件,当页面加载时,会首先创建并呈现这个组件,它就像是一扇房屋的大门,是用户进入应用页面的首要入口。一个页面有且仅有一个@Entry装饰的组件,只有被@Entry修饰的组件或者子组件才会在页面上显示。
- @State 装饰器:当被@State装饰的变量值发生改变时,会触发该变量所对应的自定义组件的 UI 界面进行刷新,实现了数据与 UI 的自动关联。例如:
@Entry
@Component
struct Page {
@State count: number = 0;
build() {
Column() {
Text(`计数: ${this.count}`)
.fontSize(20)
Button('增加')
.onClick(() => {
this.count++;
});
}
}
}
在这个例子中,当点击 “增加” 按钮时,count的值改变,UI 上显示的计数也会随之更新,让开发者无需手动操作 DOM 来更新界面,大大简化了开发过程,就像智能感应设备,数据一变,显示也自动更新。
3. 声明式 UI:ArkTS 定义了声明式 UI 描述,开发者只需描述界面 “是什么样子”,而不需要详细描述 “如何去构建”,这使得代码更加简洁、直观,易于理解和维护。例如:
@Entry
@Component
struct HelloWorld {
build() {
Row() {
Column() {
Text('Hello, World!')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.width('100%')
.height('100%');
}
}
}
}
通过这种声明式的方式,清晰地描述了一个包含文本的页面布局,而无需像命令式编程那样一步步地操作界面元素,就像在纸上画出房屋的布局图,而不是一步步描述如何砌墙、安门。
4. 状态管理:提供了多维度的状态管理机制,支持在组件内及不同组件层级间传递数据,如父子组件、爷孙组件间,甚至可在应用全局范围内或跨设备传递数据,确保数据在整个应用中的一致性和可管理性,就像一个高效的物流系统,保证货物(数据)在各个环节准确传递。
5. 渲染控制:支持条件渲染和循环渲染,开发者可以根据应用的不同状态来决定是否渲染某个组件,或者从数据源中迭代获取数据并创建相应组件。例如,根据用户是否登录来显示不同的界面:
@Entry
@Component
struct UserInfo {
@State isLoggedIn: boolean = false;
build() {
Column() {
Text(this.isLoggedIn ? '欢迎回来,用户' : '请登录')
.fontSize(20)
Button(this.isLoggedIn ? '退出登录' : '登录')
.onClick(() => {
this.isLoggedIn = !this.isLoggedIn;
});
}
}
}
这里通过条件渲染,根据isLoggedIn的状态来显示不同的文本和按钮,实现了灵活的界面展示,就像一个智能展示柜,根据不同的条件展示不同的商品。
6. 兼容性:ArkTS 兼容 TS/JavaScript 生态,开发者可以方便地使用 TS/JS 进行开发,并且能够复用已有的代码和库,这大大降低了学习成本,也充分利用了现有的开发资源,就像一个万能的转换器,可以让旧工具在新环境中继续发挥作用。
策略模式在鸿蒙中的应用实例
场景设定
为了更直观地理解策略模式在鸿蒙开发中的应用,我们以一个购物应用中的促销策略为例。在这个购物应用中,不同的节日或活动期间会有不同的促销方式,如打折、满减、赠品等。我们可以将这些不同的促销方式定义为不同的策略,通过策略模式来灵活地管理和切换这些促销策略。
代码实现
定义策略接口
首先,我们需要定义一个策略接口,该接口包含所有策略需要实现的方法。在这个例子中,我们定义一个PromotionStrategy接口,其中包含一个applyPromotion方法,用于应用促销策略。
// 定义促销策略接口
interface PromotionStrategy {
applyPromotion(price: number): number;
}
在这段代码中,我们使用interface关键字定义了PromotionStrategy接口,它声明了一个名为applyPromotion的方法,该方法接受一个price参数,表示商品的原价,返回值类型为number,表示应用促销策略后的价格。这个接口就像是一个规范,规定了所有具体促销策略类必须实现的方法。
实现具体策略
接下来,我们实现不同的促销策略类,每个类都实现PromotionStrategy接口。
- 打折策略:
// 打折策略类
class DiscountStrategy implements PromotionStrategy {
private discount: number;
constructor(discount: number) {
this.discount = discount;
}
applyPromotion(price: number): number {
return price * this.discount;
}
}
在DiscountStrategy类中,我们定义了一个私有属性discount,用于存储折扣率。构造函数接受一个discount参数,用于初始化折扣率。applyPromotion方法实现了接口中的方法,它根据传入的原价price和折扣率discount,计算并返回打折后的价格。
- 满减策略:
// 满减策略类
class FullReduceStrategy implements PromotionStrategy {
private full: number;
private reduce: number;
constructor(full: number, reduce: number) {
this.full = full;
this.reduce = reduce;
}
applyPromotion(price: number): number {
if (price >= this.full) {
return price - this.reduce;
}
return price;
}
}
FullReduceStrategy类用于实现满减策略。它有两个私有属性full和reduce,分别表示满减的条件金额和减免的金额。构造函数接受这两个参数进行初始化。applyPromotion方法中,首先判断原价price是否大于等于满减条件金额full,如果满足条件,则返回减去减免金额reduce后的价格;否则,直接返回原价。
- 赠品策略:这里我们简单处理为返回原价,实际应用中可添加赠品相关逻辑,比如记录赠品信息等。
// 赠品策略类
class GiftStrategy implements PromotionStrategy {
applyPromotion(price: number): number {
// 实际应用中可添加赠品相关逻辑
return price;
}
}
GiftStrategy类实现了赠品策略。在applyPromotion方法中,目前只是简单地返回原价,在实际应用中,可以在这里添加记录赠品信息、显示赠品相关提示等逻辑。
策略的调用与管理
在购物应用中,我们可以根据不同的场景选择不同的促销策略。以下是一个简单的示例,展示如何在代码中选择和调用不同的策略。
// 购物车类
class ShoppingCart {
private totalPrice: number;
private promotionStrategy: PromotionStrategy | null;
constructor() {
this.totalPrice = 0;
this.promotionStrategy = null;
}
// 添加商品到购物车,简单模拟计算总价
addProduct(price: number) {
this.totalPrice += price;
}
// 设置促销策略
setPromotionStrategy(strategy: PromotionStrategy) {
this.promotionStrategy = strategy;
}
// 结算
checkout() {
if (this.promotionStrategy) {
this.totalPrice = this.promotionStrategy.applyPromotion(this.totalPrice);
}
console.log(`结算价格: ${this.totalPrice}`);
// 这里可添加实际支付逻辑,如跳转到支付页面等
}
}
在ShoppingCart类中,我们定义了totalPrice属性用于存储购物车的总价,promotionStrategy属性用于存储当前应用的促销策略,初始值为null。addProduct方法用于模拟添加商品到购物车,并更新总价。setPromotionStrategy方法用于设置当前的促销策略。checkout方法是结算方法,首先判断是否设置了促销策略,如果设置了,则调用策略的applyPromotion方法计算应用促销后的价格,并更新总价,最后输出结算价格,在实际应用中,这里还可以添加跳转到支付页面等实际支付逻辑。
下面是如何使用这些类的示例:
// 使用示例
let cart = new ShoppingCart();
cart.addProduct(100);
cart.addProduct(200);
// 使用打折策略,8折
let discountStrategy = new DiscountStrategy(0.8);
cart.setPromotionStrategy(discountStrategy);
cart.checkout();
// 使用满减策略,满200减50
let fullReduceStrategy = new FullReduceStrategy(200, 50);
cart.setPromotionStrategy(fullReduceStrategy);
cart.checkout();
// 使用赠品策略
let giftStrategy = new GiftStrategy();
cart.setPromotionStrategy(giftStrategy);
cart.checkout();
在上述使用示例中,首先创建了一个ShoppingCart实例cart,并添加了两个商品,价格分别为 100 和 200。然后依次使用打折策略(8 折)、满减策略(满 200 减 50)和赠品策略进行结算,展示了如何在不同场景下灵活切换促销策略。
代码优化与注意事项
优化策略
- 减少重复代码:在实现具体策略时,仔细分析不同策略之间的共性和差异。对于共性部分,可以提取到一个基类或工具方法中,以减少重复代码。例如,在我们的促销策略示例中,如果不同促销策略在计算总价前都需要进行一些相同的商品数据预处理操作,就可以将这些预处理操作提取到一个独立的方法中,供各个策略类调用 。
- 提高性能:在选择数据结构和算法时要谨慎。例如,在购物车类中,如果商品数量较多,在计算总价等操作时,可以考虑使用更高效的数据结构和算法来提高计算速度。比如,在计算购物车中商品总价时,使用reduce方法对数组进行遍历计算,相比普通的for循环,代码更加简洁,性能也可能更优 。
// 使用reduce计算总价
let cart = new ShoppingCart();
cart.addProduct(100);
cart.addProduct(200);
let totalPrice: number = cart.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
- 策略的缓存与复用:如果某些策略在应用中会被频繁使用,可以考虑对策略实例进行缓存,避免重复创建。例如,在一个长期运行的购物应用中,打折策略和满减策略可能会被多次使用,那么可以在第一次创建这些策略实例后,将其缓存起来,后续需要使用时直接从缓存中获取,这样可以减少对象创建和销毁的开销,提高系统性能 。
- 使用泛型提升代码通用性:在定义策略接口和具体策略类时,如果策略方法的参数和返回值类型具有一定的通用性,可以使用泛型来提高代码的复用性。比如,在一个更通用的策略模式应用中,策略方法可能不仅处理数字类型的价格,还可能处理其他类型的数据,这时可以使用泛型来定义策略接口,使其能够适应不同类型的数据处理 。
// 使用泛型定义策略接口
interface GenericStrategy<T> {
applyStrategy(data: T): T;
}
注意事项
- 内存管理:在频繁创建和销毁策略对象时,要注意内存管理。确保不再使用的策略对象能够被及时回收,避免内存泄漏。例如,在购物车结算完成后,如果不再需要某个促销策略对象,应确保其引用被释放,以便垃圾回收机制能够回收其占用的内存。可以将策略对象的引用设置为null,或者使用弱引用(在 ArkTS 中如果有相关支持的话)来管理策略对象的生命周期 。
- 资源释放:如果策略实现中涉及到外部资源的使用,如文件操作、网络连接等,在策略使用完毕后,要确保这些资源被正确释放。以网络请求策略为例,在请求完成后,要关闭相关的网络连接,释放连接资源,避免资源占用导致的性能问题和潜在的错误 。
- 策略的一致性:确保不同策略之间的行为具有一致性和可预测性。在设计策略时,要明确每个策略的适用场景和预期结果,避免出现策略之间行为差异过大或不一致的情况,以免给调用者带来困惑。例如,在促销策略中,打折策略和满减策略的计算方式和结果应该是清晰明确的,并且在不同的应用场景下都能保持一致的行为 。
- 策略的扩展与维护:当需要添加新的策略时,要确保不会对现有代码造成过多的影响。遵循开闭原则,尽量通过扩展而不是修改现有代码来实现新策略的添加。同时,在维护策略代码时,要注意对策略接口和具体策略类的修改可能会影响到其他部分的代码,因此在修改时要进行充分的测试,确保整个系统的稳定性 。
总结与展望
策略模式在鸿蒙开发中展现出了强大的灵活性和可扩展性,通过将不同的业务逻辑封装成独立的策略类,使得代码的维护和扩展变得更加容易。在实际应用中,无论是购物应用中的促销策略,还是其他各类应用中的不同业务场景,策略模式都能发挥其优势,提升代码的质量和可维护性。
随着鸿蒙生态的不断发展壮大,未来策略模式有望在更多的场景中得到应用。例如,在智能家居控制应用中,不同的设备控制策略可以通过策略模式进行管理;在智能出行应用中,不同的路线规划策略、交通方式选择策略等也可以运用策略模式来实现。同时,随着 ArkTS 语言的不断发展和完善,策略模式的实现和应用也将变得更加便捷和高效。
希望通过本文的介绍,能让大家对策略模式在鸿蒙开发中的应用有更深入的理解和认识,也期待大家在自己的鸿蒙开发项目中,能够灵活运用策略模式,打造出更加优秀的应用。