1 开闭原则

在面向对象领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。通俗的来说,就是允许增加操作对象的新的功能,但是,不能去改变原来的类的结构。

2 雇员管理系统

雇员管理系统可以聘用员工、解雇员工、获取员工的信息。现在我增加一个新的功能,计算员工的离职补偿。

2.1 传统的实现方法

我们直接在对象管理类中添加一个计算离职补偿的函数。

//员工类

public class Employee {

       private String name;

       private float income;

       private int vacationDays;

       private int degree;

       public Employee(String name, float income, int vacationDays, int degree) {

              this.name = name;

              this.income = income;

              this.vacationDays = vacationDays;

              this.degree = degree;

       }

       public void setName(String name) {

              this.name = name;

       }

       public String getName() {

              return name;

       }

       public void setIncome(float income) {

              this.income = income;

       }

       public float getIncome() {

              return income;

       }

       public void setVacationDays(int vacationDays) {

              this.vacationDays = vacationDays;

       }

       public int getVacationDays() {

              return vacationDays;

       }

       public void setDegree(int degree) {

              this.degree = degree;

       }

       public int getDegree() {

              return degree;

       }

}
//雇员管理类

public class EmployeeManager {

       private HashMap<String, Employee> employees;

       public EmployeeManager() {

              employees = new HashMap<String, Employee>();

       }

       public void Attach(Employee employee) {

              employees.put(employee.getName(), employee);

       }

       public void Detach(Employee employee) {

              employees.remove(employee);

       }

       public Employee getEmployee(String name) {

              return employees.get(name);

       }

       //直接在管理类中添加新的计算离职补偿的代码

       public void getCompensation() {

              for (Employee employee : employees.values()) {

                     System.out

                                  .println(employee.getName()

                                                + "'s Compensation is "

                                                + (employee.getDegree()

                                                              * employee.getIncome()* 100));

              }

       }

}
//测试

public class MainTest {

       public static void main(String[] args) {

              EmployeeManager manager = new EmployeeManager();

              manager .Attach(new Employee("Tom", 4500, 8, 1));

              manager .Attach(new Employee("Jerry", 6500, 10, 2));

              manager .Attach(new Employee("Jack", 9600, 12, 3));

              manager .getCompensation();

       }

}

2.2 结果

2.3 存在的问题

显然,违背了开闭原则,增加了操作对象的新的功能,修改了原来的类的结构。

3 访问者模式

对传统的方式的改进的思路:我要增加作用于这些对象的新的功能,我们不在原来的类中修改代码,而是由访问者去实现该功能。

3.1 定义

访问者模式:对于一组对象,在不改变数据结构的前提下,增加作用于这些结构元素新的功能。

3.2 具体的实现

1)我们首先把访问者注入到对象的管理类中,在对象的管理类再把访问者注入到具体的对象中

2)访问者获取了对象的信息,并且具体实现新的功能

 

3.3 具体实现的代码

1)定义Visitor接口,提供访问对象的函数

public interface Visitor {

       abstract public void Visit(Employee employee );

       

}

2)我们首先把访问者注入到对象的管理类中,在对象的管理类再把访问者注入到具体的对象中

对象管理类中,添加注入函数,把visitor注入到具体的对象中

public void Accept(Visitor visitor) {

              for (Employee e : employees.values()) {

                     e.Accept(visitor);

              }

       }

3)被注入的对象把自己的信息提供给访问者

对象类中,把信息提供给访问者,this表示当前的对象    

   public void Accept(Visitor visitor) {

              // TODO Auto-generated method stub

              visitor.Visit(this);

       }

4)新的功能具体的实现

public class CompensationVisitor implements Visitor {

       @Override

       public void Visit(Employee employee) {

       

              System.out.println(employee.getName() + "'s Compensation is "

                           + (employee.getDegree() * employee.getIncome() * 10));

       }

}

3.4 代码的改进

我们的Visitor方法是访问对象,但是不应该具体制定访问对象的类型,应该是一个通用的方法。因此,我们让所有的对象都继承同一个类Element,而Visitor访问的就是Element类型的对象元素。
 

public abstract class Element {

       abstract public void Accept(Visitor visitor);

}

public interface Visitor {

       abstract public void Visit( Element element );

       

}

public class CompensationVisitor implements Visitor {

       @Override

       public void Visit(Element element) {

              // TODO Auto-generated method stub

              Employee employee = ((Employee) element);

              System.out.println(employee.getName() + "'s Compensation is "

                           + (employee.getDegree() * employee.getVacationDays() * 10));

       }

}

3.5 我们再添加一个新的功能:奖励员工

我们只需要添加一个新的Visitor实现

public class RewardVisitor implements Visitor {

       

       @Override

       public void Visit(Element element) {

              Employee employee = ((Employee) element);

              System.out.println(employee.getName() + "'s Reward is "

                           + (employee.getDegree() * employee.getIncome() * 1));

              

       }

}

3.6 访问者模式的实验结果

public class MainTest {

       public static void main(String[] args) {

              EmployeeManager mEmployees = new EmployeeManager();

              mEmployees.Attach(new Employee("Tom", 4500, 8, 1));

              mEmployees.Attach(new Employee("Jerry", 6500, 10, 2));

              mEmployees.Attach(new Employee("Jack", 9600, 12, 3));

              mEmployees.Accept(new CompensationVisitor());

              mEmployees.Accept(new RewardVisitor());

    

       }

}

4 适用场景

   如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。

   访问者的具体的实现中可能用到了某些对象属性,如果对象的属性经常发生变动,我们要不断的去修改访问者代码,所以访问者不适用于经常变动的数据结构。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐