设计模式(11)—— 外观模式

在一个复杂的软件系统中,客户端可能需要与多个复杂的业务类进行交互。这种情况下,就需要一个“服务员”的角色为客户端提供服务,即客户端只需要和“服务员”进行交互,就可以实现与业务类之间的交互。

1. 引出问题

需求:开发一个文件加密软件,可以应用在不同的系统上。整个加密过程可以拆分成三个业务:读取源文件、加密、将加密后的文件保存。这三个业务操作相对独立,故把他们封装到三个独立的类中

在实际应用中,可以让不同的系统再分别调用这三个业务模块

在这个系统设计中,存在着以下几个问题:

  1. FIleReader、CipherMachine、FileWriter三个类虽然相对独立,但是在整个文件加密的过程中,它们三个都需要被依次调用,某种程度上具有一定的整体性,在当前设计中,每次执行文件加密,都要依次与它们进行逐个交互,增加了代码的复杂度,许多代码会重复出现
  2. 如果更换一个加密类,如将CipherMachine更换为NewCipherMachine类,那么所有调用旧类的地方都要修改

2. 外观模式介绍

定义:外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口。外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。

如上图所示,图(a)中,客户端和业务模块之间的调用关系非常复杂,而图(b)中引入了外观角色,使客户端对业务模块的调用通过Facade外观角色实现,降低了系统的耦合度

下图是一个典型的外观模式的结构图

外观模式中包含以下两种角色:

  1. Facade(外观角色)
  2. SubSystem(子系统角色)

外观模式的主要目的在于降低系统的复杂程度,降低系统内部的耦合性。使得新增或删除子系统变得更加便捷。

3. 以外观模式重构案例

子系统类1:FileReader

public class FileReader {
    public String read(String fileNameSrc){
        System.out.println("读取文件,获取明文: ");
        StringBuilder sb = new StringBuilder();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(fileNameSrc);
            int data;
            while((data = fis.read()) != -1){
                sb.append((char) data);
            }
            System.out.println(sb.toString());
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在...");
        } catch (IOException e) {
            System.out.println("文件操作错误...");
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

子系统类2:CipherMachine

public class CipherMachine {
    public String encrypt(String plainText){
        System.out.println("数据加密,将明文转换为密文...");
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < plainText.length(); i++){
            char c = plainText.charAt(i);
            char enc = (char)(c % 7);
            sb.append(enc);
        }
        System.out.println(sb.toString());
        return sb.toString();
    }
}

子系统类3:FileWriter

public class FileWriter {
    public void write(String encryptStr, String fileNameDes){
        System.out.println("保存密文,写入文件...");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(fileNameDes);
            fos.write(encryptStr.getBytes());
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在...");
        } catch (IOException e) {
            System.out.println("文件操作错误...");
        } finally {
            if (fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

外观类:EncryptFacade

public class EncryptFacade {
    private FileReader reader;
    private CipherMachine cipher;
    private FileWriter writer;

    public EncryptFacade(FileReader reader, CipherMachine cipher, FileWriter writer) {
        this.reader = reader;
        this.cipher = cipher;
        this.writer = writer;
    }

    //调用其他对象的业务方法
    public void fileEncrypt(String fileNameSrc, String fileNameDes){
        String plainStr = reader.read(fileNameSrc);
        String encryptStr = cipher.encrypt(plainStr);
        writer.write(encryptStr, fileNameDes);
    }
}

客户端类:Client

public class Client {
    public static void main(String[] args) {
        EncryptFacade facade = new EncryptFacade();
        facade.fileEncrypt("src.txt", "des.txt");
    }
}

测试结果如下:

4. 抽象外观类的引入

在上述标准的外观模式下,如果需要新增、删除或更换与外观类交互的子系统类,那么必须修改外观类的源代码以改变调用,违反开闭原则。

可以引入抽象外观类,将具体外观类作为抽象外观类的子类进行实现,未来可以通过新增子类的方法来适应子系统类的变化,下面具体实例解释抽象外观类的用法

现在需要改变原有系统的加密方式,将原有取模方式的加密类CipherMachine类改为以移位方式加密的NewCipherMachine

新的加密类NewCipherMachine的代码如下:

public class NewCipherMachine {
    public String encrypt(String plainText){
        System.out.println("数据加密,将明文转换为密文...");
        StringBuilder sb = new StringBuilder();
        int key = 10; //设置秘钥,移位距离为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 <= 'Z'){
                c += key % 26;
                if(c > 'Z') c -= 26;
            }

            sb.append(c);
        }
        System.out.println(sb.toString());
        return sb.toString();
    }
}

如果不新增外观类,那么只能在原有外观类中将对CipherMachine的引用修改为对NewCipherMachine的引用,违反开闭原则

所以引入一个抽象外观类,作为具体外观类的父类,而客户端只需要针对抽象外观类进行编程

抽象外观类:AbstractEncryptFacade

public abstract class AbstractEncryptFacade {
    public abstract void fileEncrypt(String fileNameSrc, String fileNameDes);
}

具体外观类1:EncryptFacade

public class EncryptFacade extends AbstractEncryptFacade {
    private FileReader reader;
    private CipherMachine cipher;
    private FileWriter writer;

    public EncryptFacade() {
        this.reader = new FileReader();
        this.cipher = new CipherMachine();
        this.writer = new FileWriter();
    }

    //调用其他对象的业务方法
    @Override
    public void fileEncrypt(String fileNameSrc, String fileNameDes){
        String plainStr = reader.read(fileNameSrc);
        String encryptStr = cipher.encrypt(plainStr);
        writer.write(encryptStr, fileNameDes);
    }
}

具体外观类2:NewEncryptFacade

public class NewEncryptFacade extends AbstractEncryptFacade {
    private FileReader reader;
    private NewCipherMachine cipher;
    private FileWriter writer;

    public NewEncryptFacade() {
        this.reader = new FileReader();
        this.cipher = new NewCipherMachine();
        this.writer = new FileWriter();
    }

    //调用其他对象的业务方法
    @Override
    public void fileEncrypt(String fileNameSrc, String fileNameDes){
        String plainStr = reader.read(fileNameSrc);
        String encryptStr = cipher.encrypt(plainStr);
        writer.write(encryptStr, fileNameDes);
    }
}

客户端类:Client

public class Client {
    public static void main(String[] args) {
        AbstractEncryptFacade facade = new NewEncryptFacade(); //为增加灵活性,此处应用配置文件确定具体类型
        facade.fileEncrypt("src.txt", "des.txt");
    }
}

注意:在实际应用中,应该采用配置文件的方式确定创建的具体类型,此处省略

测试结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值