1.简介
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这是要有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户端对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
2.定义
外观(Facade)模式又叫作门面模式,是一种通过多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程度的复杂度,提高了程序的可维护性。
在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
3.优点
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观模式。
4.缺点
- 不能很好地限制客户使用子系统类,很容易带来未知风险。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
5.结构
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口,它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。
外观模式的基本结构:
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
结构图:
6.应用场景
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
7.代码样例
1.简单样例
class SubSystemA{
public void methodA(){
System.out.println("子系统A中的methodA()被调用!");
}
}
class SubSystemB{
public void methodB(){
System.out.println("子系统B中的methodB()被调用!");
}
}
class SubSystemC{
public void methodC(){
System.out.println("子系统C中的methodC()被调用!");
}
}
class Facade{
private SubSystemA systemA = new SubSystemA();
private SubSystemB systemB = new SubSystemB();
private SubSystemC systemC = new SubSystemC();
public void method(){
systemA.methodA();
systemB.methodB();
systemC.methodC();
}
}
public class SimpleFacadeTest {
public static void main(String[] args){
Facade facade = new Facade();
facade.method();
}
}
2.应用实例
使用外观模式设计一个特产的选购界面。
分析:本实例的外观角色Specialty类是JPanle的子类,它拥有8个子系统角色Specialty1-Specailty8,它们是图标类(ImageIcon)的子类对象,用来保存特产的图片。
外观类(Specailty)是JTree组件用来管理特产的名称,并定义一个时间处理方法valueChanged(TreeSelectionEvent e),当用户从树中选择特产时,该特产的图标对象保存在标签(JLabd)对象中。
客户窗体对象用分割面板来实现,左边放外观角色的目标树,右边放显示所选特产图像的标签。
结构图如下:
代码如下:
class Specialty extends JPanel implements TreeSelectionListener{
final JTree tree;
JLabel label;
private Specialty1 specialty1 = new Specialty1();
private Specialty2 specialty2 = new Specialty2();
private Specialty3 specialty3 = new Specialty3();
private Specialty4 specialty4 = new Specialty4();
private Specialty5 specialty5 = new Specialty5();
private Specialty6 specialty6 = new Specialty6();
private Specialty7 specialty7 = new Specialty7();
private Specialty8 specialty8 = new Specialty8();
Specialty() {
DefaultMutableTreeNode top = new DefaultMutableTreeNode("特产");
DefaultMutableTreeNode node1 = null;
DefaultMutableTreeNode node2 = null;
DefaultMutableTreeNode tempNode = null;
node1 = new DefaultMutableTreeNode("主打特产(红、绿、黑、白)");
tempNode = new DefaultMutableTreeNode("荷包红鲤鱼");
node1.add(tempNode);
tempNode = new DefaultMutableTreeNode("婺源绿茶");
node1.add(tempNode);
tempNode = new DefaultMutableTreeNode("龙尾砚");
node1.add(tempNode);
tempNode = new DefaultMutableTreeNode("江湾雪梨");
node1.add(tempNode);
top.add(node1);
node2 = new DefaultMutableTreeNode("其他特产");
tempNode = new DefaultMutableTreeNode("酒糟鱼");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("糟米子糕");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("清明果");
node2.add(tempNode);
tempNode = new DefaultMutableTreeNode("油煎灯");
node2.add(tempNode);
top.add(node2);
tree = new JTree(top);
tree.addTreeSelectionListener(this);
label = new JLabel();
}
@Override
public void valueChanged(TreeSelectionEvent e) {
if(e.getSource() == tree){
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if(node == null){
return;
}
if(node.isLeaf()){
Object object = node.getUserObject();
String select = object.toString();
label.setText(select);
label.setHorizontalTextPosition(JLabel.CENTER);
label.setVerticalTextPosition(JLabel.BOTTOM);
if(select.startsWith("荷包")){
label.setIcon(specialty1);
}else if(select.startsWith("婺源")){
label.setIcon(specialty2);
}else if(select.startsWith("龙尾")){
label.setIcon(specialty3);
}else if(select.startsWith("江湾")){
label.setIcon(specialty4);
}else if(select.startsWith("酒糟")){
label.setIcon(specialty5);
}else if(select.startsWith("糟米")){
label.setIcon(specialty6);
}else if(select.startsWith("清明")){
label.setIcon(specialty7);
}else if(select.startsWith("油煎")){
label.setIcon(specialty8);
}
label.setHorizontalAlignment(JLabel.CENTER);
}
}
}
}
class Specialty1 extends ImageIcon{
Specialty1(){
super("src/facade/img/Specialty11.jpg");
}
}
class Specialty2 extends ImageIcon{
Specialty2(){
super("src/facade/img/Specialty12.jpg");
}
}
class Specialty3 extends ImageIcon{
Specialty3(){
super("src/facade/img/Specialty13.jpg");
}
}
class Specialty4 extends ImageIcon{
Specialty4(){
super("src/facade/img/Specialty14.jpg");
}
}
class Specialty5 extends ImageIcon{
Specialty5(){
super("src/facade/img/Specialty21.jpg");
}
}
class Specialty6 extends ImageIcon{
Specialty6(){
super("src/facade/img/Specialty22.jpg");
}
}
class Specialty7 extends ImageIcon{
Specialty7(){
super("src/facade/img/Specialty23.jpg");
}
}
class Specialty8 extends ImageIcon{
Specialty8(){
super("src/facade/img/Specialty24.jpg");
}
}
public class SpecialtyFacade {
public static void main(String[] args){
JFrame jFrame = new JFrame("外观模式:特产选择测试");
Container container = jFrame.getContentPane();
Specialty specialty = new Specialty();
JScrollPane treeView = new JScrollPane(specialty.tree);
JScrollPane scrollPane = new JScrollPane(specialty.label);
//分割面板
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
true, treeView, scrollPane);
//设置splitPane分割线的位置
splitPane.setDividerLocation(230);
//设置splitPane可以展开或收起
splitPane.setOneTouchExpandable(true);
container.add(splitPane);
jFrame.setSize(650, 350);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
3.外观模式的扩展
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题。
结构图:
代码样例:
class SubSystem1{
public void method1(){
System.out.println("子系统1的方法1");
}
}
class SubSystem2{
public void method2(){
System.out.println("子系统2的方法2");
}
}
class SubSystem3{
public void method3(){
System.out.println("子系统3的方法3");
}
}
class SubSystem4{
public void method4(){
System.out.println("子系统4的方法4");
}
}
interface IFacade{
void method1();
void method2();
}
class FacadeA implements IFacade{
private SubSystem1 subSystem1 = new SubSystem1();
private SubSystem2 subSystem2 = new SubSystem2();
private SubSystem3 subSystem3 = new SubSystem3();
@Override
public void method1() {
subSystem1.method1();
subSystem2.method2();
subSystem3.method3();
}
@Override
public void method2() {
subSystem3.method3();
subSystem2.method2();
subSystem1.method1();
}
}
class FacadeB implements IFacade{
private SubSystem2 subSystem2 = new SubSystem2();
private SubSystem3 subSystem3 = new SubSystem3();
private SubSystem4 subSystem4 = new SubSystem4();
@Override
public void method1() {
subSystem2.method2();
subSystem3.method3();
subSystem4.method4();
}
@Override
public void method2() {
subSystem4.method4();
subSystem3.method3();
subSystem2.method2();
}
}
public class FacadeExtend {
public static void main(String[] args){
IFacade facadeA = new FacadeA();
facadeA.method1();
IFacade facadeB = new FacadeB();
facadeB.method2();
}
}
4.使用外观模式整合调用已知API
现在准备上线一个积分兑换礼品的商城,这个商城中的大部分功能都不需要重新开发,而是要去对接已有的各个子系统。
这些子系统可能涉及积分系统、支付系统、物流系统的接口调用。但是如果全部由前端发送网络请求去调用现有接口,一会增加前端开发人员的难度,二则会增加一些网络请求,影响页面性能。
此时就可以发挥外观模式的优势了。将所有现有的接口全部整合到一个类中,由后端提供统一的接口供前端调用,这样一来前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页面交互上。
下面用代码来模拟以上提到的场景。
/**
* 礼物的实体类
*/
class GiftInfo{
@Getter
@Setter
private String name;
public GiftInfo(String name){
this.name = name;
}
}
/**
* 积分系统类
*/
class IntegralService{
public boolean isAvailable(GiftInfo giftInfo){
System.out.println("校验" + giftInfo.getName() + "积分资格通过,库存通过");
return true;
}
}
/**
* 支付系统类
*/
class PayService{
public boolean pay(GiftInfo pointGift){
//扣减积分
System.out.println("支付" + pointGift.getName() + "积分成功");
return true;
}
}
/**
* 物流系统类
*/
class LogisticsService{
public String delivery(GiftInfo giftInfo){
System.out.println(giftInfo.getName() + "进入物流系统");
String shippingOrderNo = "88888888";
return shippingOrderNo;
}
}
/**
* 外观角色GiftExchangeService
* 对外只开放一个方法exchange()
* 在exchange()方法内部整合3个子系统的所有功能
*/
class GiftExchangeService{
private IntegralService integralService = new IntegralService();
private PayService pointsPayService = new PayService();
private LogisticsService logisticsService = new LogisticsService();
public void exchange(GiftInfo giftInfo){
if(integralService.isAvailable(giftInfo)){
//资格校验通过
if(pointsPayService.pay(giftInfo)){
//如果支付积分成功
String shippingOrderNo = logisticsService.delivery(giftInfo);
System.out.println("物流系统下单成功过,订单号是:" + shippingOrderNo);
}
}
}
}
public class GiftExchangeText {
public static void main(String[] args){
GiftInfo giftInfo = new GiftInfo("积分礼物A");
GiftExchangeService service = new GiftExchangeService();
service.exchange(giftInfo);
}
}