设计模式之17门面模式(笔记)

本文介绍门面模式的概念、优缺点及应用场景,通过示例详细解释其实现方式,并探讨了门面模式在系统设计中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 定义:

1.1 定义:(Façade PatternProvide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.(要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。)简言之,门面对象是外界访问子系统内部的唯一通道,力求“金玉其外,败絮其中”。

1.2 通用类图:


Façade门面角色:客户端可以调用这个角色的方法。它知晓子系统的所有功能和责任。该角色没有实际的业务逻辑,只是一个委托类,将客户端发来的请求委派到相应的子系统去。

Subsystem子系统角色:可以同时有一个或多个子系统。每个子系统都是一个类的集合。子系统不知道门面的存在,门面相对于子系统仅是另外一个客户端而已。

1.3 通用代码:

public class ClassA {
	public void doSomethingA() {
		// 业务逻辑
	}
}

public class ClassB {
	public void doSomethingB() {
		// 业务逻辑
	}
}

public class ClassC {
	public void doSomethingC() {
		// 业务逻辑
	}
}

public class Facade {
	// 被委托的对象
	private ClassA a = new ClassA();
	private ClassB b = new ClassB();
	private ClassC c = new ClassC();

	// 提供给外部访问的方法
	public void methodA() {
		this.a.doSomethingA();
	}

	public void methodB() {
		this.b.doSomethingB();
	}

	public void methodC() {
		this.c.doSomethingC();
	}
}


2 优点

2.1 减少系统的相互依赖:把外界对子系统的依赖转为对门面的依赖,与子系统无关。

2.2 提高了灵活性:子系统作变化,只要不影响到门面对象即可。

2.3 提高了安全性:外界只能访问到门面上开通的方法,而不能触及子系统的其它业务。

3 缺点

不符合开闭原则,一旦在系统投产后发现错误,就只能修改门面角色的代码,风险大需考虑。

4 应用场景

4.1 为一个复杂的模块或子系统提供一个供外界访问的接口;

4.2 子系统相对立——外界对子系统的访问只要黑箱操作即可;

4.3 预防低水平人员开带来的风险扩散(为其限定某一子系统,供其开发)。

5 注意事项

5.1 一个子系统可以有多个门面

A。门面已经在到不能忍受的程度(比如一个门面对象已经超过了200行代码),虽然都是非常简单的委托操作,也建议拆分成多个门面,否则会给以后的维护和扩展带来不必要的麻烦。如何拆分?可以按功能:如数据库操作的门面可以分为查询门面、删除门面、更新门面等。

B。子系统可以提供不同访问路径:以通用源码为例,ClassAClassBClassC是一个子系统中的3个对象,现在有两个不同的高层模块来访问该子系统,模块一是子系统的信任模块,可以完整的访问子系统的所有业务逻辑;而模块二属于受限访问对象,只能访问methodB方法。此时,就需要建立两个门面以供不同的高层模块来访问,即在原有代码中增加一个门面即可。代码如下:

public class Facade2 {
	//引用原有的门面
	private Façade façade = new Façade();
	//对外提供唯一的访问子系统的方法
	public void methodB(){
		This.facade.methodB();
	}
}


为什么要使用委托而不再编写一个委托到子系统的方法呢?那是因为在面向对象的编程中,尽量保持相同的代码只编写一遍,避免以后到处修改相似代码的悲剧

5.2 门面不参与子系统内的业务逻辑

以通用代码为例:如果methodC中必须先调用ClassAdoSomethingA,然后再调用 ClassC doSomethingC方法呢?如何改。但千万别这么做

public void methodC( ) {
	this.a.doSomethingA();
	this.c.doSomethingC();
}

为什么不要这么做呢?因为你已经让门面对象参与了业务逻辑,而门面对象只提供了个访问子系统的一个路径而已。否则就会产生一个倒依赖的问题:子系统必须依赖门面才能被访问,这是设计上的一个严重错误,不仅违反了单一职责原则,同时也破坏了系统的封装性。

解决之道:建立一个封装类,封装完毕后提供给门面对象。

最终修改如下:

public class Context {
	// 委托处理
	private ClassA a = new ClassA();
	private ClassC c = new ClassC();

	// 复杂的计算
	public void complexMethod() {
		this.a.doSomethingA();
		this.c.doSomethingC();
	}
}

public class Facade {
	// 被委托的对象
	private ClassA a = new ClassA();
	private ClassB b = new ClassB();
	private Context context = new Context();

	// 提供给外部访问的方法
	public void methodA() {
		this.a.doSomethingA();
	}

	public void methodB() {
		this.b.doSomethingB();
	}

	public void methodC() {
		this.context.complexMethod();
	}
}

6 扩展

暂无

7 范例

7.1 用户登陆验证的门面小例。

类图


源代码

package _17_Facade.mixed;
public interface ILoginCheck {
	public boolean login(String user, String password);
}

public class LoginCheck implements ILoginCheck {
	private Database db;

	@Override
	public boolean login(String user, String password) {
		db = new Database();//
		// 检查格式
		if (!FormatCheck.isUserNameValid(user))
			return false;
		if (!FormatCheck.isPasswordValid(password))
			return false;
		// 检查是否存在用户
		if (!isUserExist(user))
			return false;
		// 检查密码是否正确
		if (!password.equals(this.getUserPassword(user)))
			return false;
		return true;
	}

	private boolean isUserExist(String user) {
		return db.isUserExist(user);
	}

	private String getUserPassword(String user) {
		return db.getUserPassword(user);
	}
}

public class FormatCheck {
	public static boolean isUserNameValid(String user) {
		return true;
	}

	public static boolean isPasswordValid(String password) {
		return true;
	}
}

public class Database {
	public boolean isUserExist(String user) {
		return true;
	}

	public String getUserPassword(String user) {
		return new String("123456");
	}
}

public class Login {
	// private LoginCheck check = new LoginCheck();
	private ILoginCheck check = new LogDecorator(new LoginCheck());

	public boolean login(String user, String password) {
		return check.login(user, password);
	}
}

public class Client {
	public static void main(String args[]) {
		Login login = new Login();
		boolean result = login.login("Tom", "123456");
		System.out.println("登陆帐号:Tom , 123456 .结果为: " + result);
	}
}

结果如下:

登陆帐号:Tom , 123456 .结果为 : true

7.2 承上例,业务变化(为加强系统安全),系统需要记录每次登陆的用户名。(在子系统内扩张,这里因为系统已经投产,就小小地结合使用一下装饰模式吧)

类图如下:


源代码(仅需添加如下两类,其它无需作任何变化)

import java.util.Date;
import java.text.DateFormat;
public class Decorator implements ILoginCheck {

	protected ILoginCheck check;

	public Decorator(ILoginCheck check) {
		this.check = check;
	}

	@Override
	public boolean login(String user, String password) {
		return check.login(user, password);
	}
}

public class LogDecorator extends Decorator {
	public LogDecorator(ILoginCheck check) {
		super(check);
	}

	public void writeLog(String user) {
		Date now = new Date();
		DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
				DateFormat.MEDIUM);
		// 显示日期,时间(精确到分)
		System.out.println("---------" + user + " login at " + df.format(now)
				+ "----------");
	}

	@Override
	public boolean login(String user, String password) {
		boolean result = super.login(user, password);
		if (result)
			writeLog(user);
		return result;
	}
}

结果如下:

---------Tom login at 2012-5-17 17:24:03----------

登陆帐号:Tom , 123456 .结果为 : true

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值