设计模式之七 适配器模式与外观模式

  • 适配器模式与外观模式

定义

 适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

形象的说,如果你在国内使用一台美版的笔记本,那么你必然需要一个交流电适配器,适配器的作用就是将一种接口转换成另一种接口,以符合客户的期望。

这个模式可以通过创建适配器进行接口转换,让不兼容的接口变成兼容。这可以让客户从实现的接口解耦。


外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

优缺点

优点:被适配者的任何子类,都可以搭配着适配器使用

应用场景

 

例子

假设以有偿一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新的厂商所设计出来的接口,不同于旧厂商的接口;因此我们需要一个适配器类将新厂商的接口转换成你所期望的接口。

鸭子适配器:将火鸡适配为鸭子

public interface Duck {
	public void quack();
	public void fly();
}

 绿头鸭实现

public class MallardDuck implements Duck {

	@Override
	public void quack() {
		System.out.println("Quack");
	}

	@Override
	public void fly() {
		System.out.println("I'm flying");
	}
}

火鸡接口(被适配者)

public interface Turkey {
	public void gobble();
	public void fly();
}

火鸡实现类

public class WindTurkey implements Turkey {

	@Override
	public void gobble() {
		System.out.println("Gooble");
	}

	@Override
	public void fly() {
		System.out.println("I'm flying a short distance");
	}
}

鸭子适配器(适配器)

public class TurkeyAdapter implements Duck {
	private Turkey turkey;
	public TurkeyAdapter(Turkey turkey) {
		this.turkey = turkey;
	}
	
	@Override
	public void quack() {
		turkey.gobble();
	}

	@Override
	public void fly() {
		for(int i = 0; i < 5; i++) {
			turkey.fly();
		}
	}
}

测试

public class DuckTestDrive {

	public static void main(String[] args) {
		MallardDuck duck = new MallardDuck();
		
		WindTurkey turkey = new WindTurkey();
		Duck turkeyAdapter = new TurkeyAdapter(turkey);
		
		System.out.println("The Turkey says ...");
		turkey.gobble();
		turkey.fly();
		
		System.out.println("The Duck says ...");
		testDuck(duck);
		
		System.out.println("The TurkeyAdapter says ...");
		testDuck(turkeyAdapter);
	}

	static void testDuck(Duck duck) {
		duck.quack();
		duck.fly();
	}
}

测试结果 

实际上有两种适配器:“对象”适配器和“类”适配器,前面是“对象”适配器;“类”适配器在Java中是不可能的,因为他需要使用多重继承才能实现。


真实案例:

Java早期的集合(collection)类型(如:Vector、Stack、Hashtable)都实现了一个名为elements()的方法。该方法会返回一个Enumeration(枚举)。这个Enumeration接口可以逐一走过此集合内的每一个元素,而无需知道他们在集合内是如何被管理的。

序号方法描述
0boolean hasMoreElements( )
测试此枚举是否包含更多的元素。
1Object nextElement( )
如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。

当Sun推出更新后的集合类时,开始使用了Iterator(迭代器)接口,这个接口和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器还提供了删除元素的能力。

序号方法描述
0boolean hasNext( )
测试此迭代集合是否包含下一个元素。
1Object next( )
返回集合中当前游标右侧的元素
2void remove( )
删除集合中当前游标左侧的元素

如今我们希望在新的代码中只使用迭代器,所以我们需要塑造一个适配器。

适配器的hasNext( )next( )方法很容易实现,对于remove( )方法,因为枚举是一个“只读”接口,所以我们需要把remove( )方法定义成会抛出UnsupportedOperationExeception。

public class EnumerationIterator<T> implements Iterator<T> {
	Enumeration<T> enumeration;
	
	public EnumerationIterator(Enumeration<T> enumeration) {
		this.enumeration = enumeration;
	}

	@Override
	public boolean hasNext() {
		return enumeration.hasMoreElements();
	}

	@Override
	public T next() {
		return enumeration.nextElement();
	}
	
	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}
}

外观模式就是在创建一个接口简化而统一的类,用来包装系统中一个或多个复杂的类。外观模式相当直接,很容易理解,此处就不再赘述。

 

对比

序号模式意图
0装饰者不改变接口,但加入责任
1适配器将一个接口转成另一个接口
2外观让接口更简单

 

设计原则

封装变化

多用组合,少用继承

针对接口编程,不针对实现编程

为交互对象之间的松耦合设计而努力

类应该对扩展开放,对修改关闭

依赖抽象,不依赖具体类

最少知识原则:只和朋友交谈 

总结

 当需要使用一个现有类而其接口并不符合你的需要时,就使用适配器;适配器改变接口以符合客户的期望。

当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观;外观将客户从一个复杂的子系统中解耦。

实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行。

小知识

 最少知识原则(墨忒尔法则Law of Demeter):只和你的密友谈话。

这个原则不希望过多的类耦合在一起,否则会造成系统太复杂而不容易被维护。

在一个对象的方法内,我们只应该调用属于以下范围的方法:

该对象本身;

被当做方法的参数而传递进来的对象;

此方法所创建或实例化的任何对象;(前三个方针告诉我们,如果某对象是调用其他的方法的返回结果,不需要调用该对象的方法)

对象的任何组件。(把“组件”想象成是被实例变量所引用的任何对象,换句话说,把这想象成是“有一个”(HAS-A)关系)

比方说:

不建议使用这个原则

public float getTemp() {
    Thermometer thermometer = station.getThermometer();
    return thermometer.getTemperature();
}

采用这个原则

public float getTemp() {
    return station.getTemperature();
}

优点:减少对象依赖,减少维护成本。

缺点:会导致更多的“包装”类被制造出来,以处理和其他组件的沟通,可能会导致复杂度和开发时间的增加,并降低运行时的性能。

<think>好的,我现在需要帮助用户解决关于Dify循环实现的问题。首先,我需要明确用户的具体需求。他们提到“dify 循环实现”或“dify 如何处理循环”,但Dify本身可能没有直接称为“循环”的功能,所以需要结合现有文档中的信息来推断可能的解决方案。 根据用户提供的引用内容,Dify工作流中有迭代节点[^1]。这可能类似于编程中的循环结构,用于重复执行某个任务。例如,在处理列表数据或需要多次执行同一操作时,迭代节点可以派上用场。我需要详细解释迭代节点的配置和使用步骤,确保用户能够理解如何设置循环次数或遍历数据集。 另外,用户可能对如何处理循环中的错误或性能问题感兴趣。引用[2]提到在Dify中处理PEM文件的问题,这可能涉及在循环中正确处理数据格式,避免错误积累。需要提醒用户在循环中注意错误处理和资源管理,确保每次迭代的稳定性。 引用[3]中的例子展示了如何结合FastAPI和Selenium进行实时监控并生成GIF,这可能涉及循环截取浏览器内容。虽然Dify本身可能不直接控制这类循环,但可以通过外部脚本或定时任务触发工作流,模拟循环执行。这部分需要说明如何将外部循环机制与Dify结合使用。 接下来,我需要组织回答结构。首先介绍迭代节点的基本概念和配置步骤,然后讨论循环中的数据处理和错误处理,最后补充外部触发循环的方法。同时,生成相关问题以帮助用户进一步探索,比如优化循环性能或处理复杂数据结构。 需要确保回答符合格式要求,使用正确的中文表达,行内公式用$...$,独立公式用$$...$$,但当前问题不涉及数学公式,所以可能不需要使用。同时,引用标识要自然地添加到相关段落末尾,如、[^2]、[^3]。 最后,检查是否有遗漏点,比如Dify的其他循环相关功能或用户可能遇到的常见问题,确保回答全面且易于理解。</think>在Dify中处理循环逻辑主要通过**迭代节点**实现,以下是具体实现方式和应用场景的解析: ### 一、Dify循环实现机制 Dify通过**工作流设计器**中的迭代节点处理循环需求,其核心原理类似编程中的`for循环`。迭代节点可遍历以下数据类型: - 数组列表:`["A","B","C"]` - 字典集合:`{"key1":"value1", "key2":"value2"}` - 数值范围:通过`range()`函数生成序列 配置示例: ```python # 模拟迭代节点的数据输入 input_data = { "dataset": [1,2,3,4,5], "process_logic": "item * 2" # 对每个元素执行乘以2的操作 } ``` ### 二、迭代节点的关键配置步骤 1. **数据源绑定**:将数组/字典类型变量连接到迭代节点的输入端口 2. **循环变量命名**:设定当前元素的变量名(默认为`item`) 3. **子流程设计**:在迭代节点内部构建需要重复执行的逻辑模块 4. **结果聚合**:通过`outputs`收集所有迭代结果,支持数组或对象格式 $$ \text{总耗时} = \sum_{i=1}^{n}(单次迭代时间_i) + 系统开销 $$ ### 三、循环中的特殊处理 1. **错误中断控制**: - 启用`continueOnError`参数可跳过失败迭代 - 通过`try-catch`模块包裹敏感操作 2. **并行优化**: ```python # 伪代码示例 Parallel.forEach(dataset, lambda item: process(item)) ``` 3. **结果过滤**: ```python filtered = filter(lambda x: x%2==0, processed_results) ``` ### 四、应用场景案例 1. **批量文件处理**:遍历存储桶中的文件列表进行格式转换 2. **数据清洗**:对数据库查询结果集进行逐条校验 3. **API轮询**:定时循环调用第三方接口直到满足特定条件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值