外观模式
外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口,外观模式定义了一个高层接口,这一高层接口使得这个子系统更容易的使用。外观模式又称为门面模式,是一种对象结构性模型。
上述代码存在如下问题:
(1)FileReader类、CipherMachine类、FileWriter类经常会作为一个整体同时出现,客户端代码需要与他们逐个进行交互,导致客户端代码较为复杂,而且每次使用都会有出现。
(2)如果需要更换一个加密类,例如将CipherMachine类更换成NewCipherMachine类,则所有使用该文件加密模块的代码都需要进行修改,系统维护难度极大,灵活性和可扩展性较差。
可以使用外观模式,对其进行重构,增加一个外观类,由外观类来封装业务之间的交互,而客户端只需要与外观类交互即可。
我们先来复习一下迪米特法则
一个实体应当尽量减少与其他实体发生相互作用
如果一个系统满足迪米特原则,那么当一个模块发生改变时,就会尽可能小的影响其他模块,扩展就会相对容易,迪米特罚要求限制软件实体间的的宽度和深度,降低耦合度。
外观模式的目标在是客户端与子系统之间的通信和相互依赖关系到最小,为子系统的访问提供一个简单而单一的入口,降低客户端与子系统的耦合度。
(1)Facade(外观角色)客户端可以调用这个角色的方法,外观类中可以知道相关的(一个或者多个)子系统的功能和职责,在正常情况下,它将所有客户端发过来的请求外派到相应的子系统中去,传递给相应的子系统去处理。
(2)SubSystem(子系统角色)在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,实现子系统的功能,都可以被客户端调用或者外观角色调用,处理由外观类传过来的请求,子系统并不知道外观的存在。对于子系统而言,外观角色是其客户端。
答:客户端只跟外观角色作为朋友,外观角色跟不同的子系统作为朋友。
完整的解决方案
package com.learn.designmode.mode.facade;
/**
* 数据加密类
*/
public class CipheMachine {
public String encrypt(String plainText){
System.out.println("数据开始加密,明文转换成密文");
String es = "";
for (int i = 0;i<plainText.length();i++){
String c = String.valueOf(plainText.charAt(i) % 7);
es += c;
}
System.out.println(es);
return es;
}
}
package com.learn.designmode.mode.facade;
import java.io.FileInputStream;
/**
* 文件读取类,子系统类
*/
public class FileReader {
public String read(String fileNameSrc){
System.out.println("读取文件,获取明文");
StringBuffer sb = new StringBuffer();
try {
FileInputStream fileInputStream = new FileInputStream(fileNameSrc);
int data;
while ((data = fileInputStream.read()) != -1){
sb.append((char)data);
}
fileInputStream.close();
System.out.println(sb.toString());
}catch (Exception ex){
System.out.println("文件操作错误");
}
return sb.toString();
}
}
package com.learn.designmode.mode.facade;
import java.io.FileOutputStream;
public class FileWriter {
public void write(String encyptyStr,String fileName){
System.out.println("保存文件,写入文件");
try {
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
fileOutputStream.write(encyptyStr.getBytes());
fileOutputStream.close();
}catch (Exception ex){
System.out.println("文件写入异常");
}
}
}
package com.learn.designmode.mode.facade;
/**
* 加密外观类
*/
public class EncryptFacade {
private FileReader fileReader;
private FileWriter fileWriter;
private CipheMachine cipheMachine;
public EncryptFacade(){
fileReader = new FileReader();
fileWriter = new FileWriter();
cipheMachine = new CipheMachine();
}
// 调用其他子系统业务
private void fileEncrypt(String fileNameStr,String fileNameDes){
String plainStr = fileReader.read(fileNameStr);
String encryptStr = cipheMachine.encrypt(plainStr);
fileWriter.write(encryptStr,fileNameDes);
}
public static void main(String[] args) {
new EncryptFacade().fileEncrypt("E:\\learn\\java_learning\\design\\designmode\\src\\main\\resources\\static\\test.txt","E:\\learn\\java_learning\\design\\designmode\\src\\main\\resources\\static\\test1.txt");
}
}
抽象外观类的引入
package com.learn.designmode.mode.facade;
/**
* 基于移位运算的数据加密:子系统类
*/
public class NewCipheMachine {
public String encrypt(String plainText){
System.out.println("数据开始加密,明文转换成密文");
String es = "";
int key = 10;
for (int i = 0;i<plainText.length();i++){
char c = plainText.charAt(i);
// 小写字母移位
if (c >= 'a' && c <='z'){
c += key % 26;
if (c > 'z') c -= 26;
if (c < 'a') c += 26;
}
if (c >= 'A' && c <= 'Z'){
c += key % 26;
if (c > 'Z') c -= 26;
if (c < 'A') c += 26;
}
es += c;
}
System.out.println(es);
return es;
}
}
package com.learn.designmode.mode.facade.abstractFacade;
// 抽象外观类
public abstract class AbstractEncryptFacade {
// 抽象业务方法
public abstract void fileEncrypt(String fileNameStr,String fileNameDes);
}
package com.learn.designmode.mode.facade.abstractFacade;
import com.learn.designmode.mode.factory.chart.utils.XMLUtil;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
public class Client {
public static void main(String[] args) throws SAXException, IllegalAccessException, IOException, InstantiationException, ParserConfigurationException, ClassNotFoundException {
AbstractEncryptFacade abstractEncryptFacade = (AbstractEncryptFacade) XMLUtil.getBean("facade");
abstractEncryptFacade.fileEncrypt("E:\\learn\\java_learning\\design\\designmode\\src\\main\\resources\\static\\test.txt","E:\\learn\\java_learning\\design\\designmode\\src\\main\\resources\\static\\test1.txt");
}
}
package com.learn.designmode.mode.facade.abstractFacade;
import com.learn.designmode.mode.facade.FileReader;
import com.learn.designmode.mode.facade.FileWriter;
import com.learn.designmode.mode.facade.NewCipheMachine;
/**
* 加密外观类
*/
public class NewEncryptFacade extends AbstractEncryptFacade {
private FileReader fileReader;
private FileWriter fileWriter;
private NewCipheMachine cipheMachine;
public NewEncryptFacade(){
fileReader = new FileReader();
fileWriter = new FileWriter();
cipheMachine = new NewCipheMachine();
}
private static class HolderClass{
private static AbstractEncryptFacade abstractEncryptFacade= new NewEncryptFacade();
}
// 调用其他子系统业务
@Override
public void fileEncrypt(String fileNameStr, String fileNameDes){
String plainStr = fileReader.read(fileNameStr);
String encryptStr = cipheMachine.encrypt(plainStr);
fileWriter.write(encryptStr,fileNameDes);
}
public static AbstractEncryptFacade getInstance(){
return HolderClass.abstractEncryptFacade;
}
}
外观模式补充说明
(1)外观类要单例模式。很多情况下为了节约系统资源,系统中只需要一个实例,因此可以通过单例模式设计外观类,从而保证系统只有唯一的访问子系统的入口,降低对系统资源的消耗。
(2)一个系统中可以设计多个外观类,每一个外观类负责与一些子系统交互,向客户端提供相应的业务功能。
(3)视图通过外观类为子系统增加新行为是错的,外观模式的用意在于为子系统提供一个集中化和简化的沟通渠道,而不是向子系统增加新的行为。新行为应该增加子系统新类或者修改原有子系统来实现。不能通过外观类实现。
总结
优点
(1)对客户端屏蔽子系统组件,减少了客户端所需处理的对象并使得子系统使用起来更加的容易,通过外观模式,客户端的代码将变得很简单,与之关联的对象很很少。
(2)实现了子系统和客户端之前的松耦合关系,使得子系统的变化不会调用其他的子系统,只需调整外观类即可。
(3)一个系统的修改对其他子系统没有任何影响, 子系统的内部变化不会影响到外观模式。
(4)只是提供了一个访问子系统的统一入口,并不影响客户端直接使用子系统。
缺点
(1)不能很好的限制客户端直接使用子系统,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
(2)如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
适用场景
(1)当要为一系列复杂的子系统提供一个简单入口时可以使用外观模式。
(2)客户端程序与多个子系统之间存在很大的依赖性,引入外观类可以将子系统与客户端解耦,从而提供子系统的独立性和可移植性。
(3)在层次结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不产生联系,而是通过外观类建立联系,降低层之间的耦合度。