设计模式—访问者模式

 需求:店铺采购了一批水果(苹果及橘子),现在市场监督局来店里检查过期的水果。

public class Fruit {

    private String name;
    private Date pickDate;

    public Fruit(String name, Date pickDate) {
        this.name = name;
        this.pickDate = pickDate;
    }

    public String getName() {
        return name;
    }

    public Date getPickDate() {
        return pickDate;
    }

    @Override
    public String toString() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        return "{" +
                "name='" + name + '\'' +
                ", 采摘日期=" + simpleDateFormat.format(pickDate) +
                '}';
    }
}

public class Apple extends Fruit{

    public Apple(String name, Date pickDate) {
        super(name, pickDate);
    }
    
}

public class Orange extends Fruit{
    public Orange(String name, Date pickDate) {
        super(name, pickDate);
    }
}

public class FruitShop  {

    private final String name;

    public FruitShop(String name) {
        this.name = name;
    }

    private final List fruitList = new ArrayList<>();

    public List getFruitList() {
        return fruitList;
    }

    public String getName() {
        return name;
    }

}

public class FruitMarket {

    public static void main(String[] args) throws ParseException {
        FruitShop myShop = new FruitShop("老果农");
        purchaseFruit(myShop);
        expCheck("市场监督局",myShop); //市场监督局检查过期水果:苹果过期时间5天,橘子过期时间10天
        expCheck("城管",myShop); //城管检查过期水果:苹果过期时间3天,橘子过期时间6天
        newArrival(myShop);//新品上市水果
    }

    // 采购水果
    public static void purchaseFruit(FruitShop shop) throws ParseException {
        List fruitList = shop.getFruitList();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        fruitList.add(new Apple("富士康",simpleDateFormat.parse("2023-06-21 12:00")));
        fruitList.add(new Orange("赣南脐橙", simpleDateFormat.parse("2023-05-25 18:00")));
        fruitList.add(new Orange("韶关帝王橘", simpleDateFormat.parse("2023-6-18 21:00")));
        fruitList.add(new Apple("王掌柜",simpleDateFormat.parse("2023-06-11 12:00")));
    }

    // 过期检查
    public static void expCheck(String development,FruitShop shop) {
        int appleExpDay = 0, orangeExpDay = 0;
        if ("市场监督局".equals(development)) {
            appleExpDay = 5;
            orangeExpDay = 10;
        } else if ("城管".equals(development)) {
            appleExpDay = 2;
            orangeExpDay = 4;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE,-appleExpDay);
        Date appleExpDate = calendar.getTime();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE,-orangeExpDay);
        Date orangeExpDate = calendar.getTime();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        System.out.println(development + "检查 苹果过期天数:" + appleExpDay + "  橘子过期天数:" + orangeExpDay + "  检查时间:" + dateFormat.format(new Date()) + " " + shop.getName());
        for (Fruit fruit : shop.getFruitList()) {
            boolean exp = false;
            if (fruit instanceof Apple) {
                if (appleExpDate.after(fruit.getPickDate())) exp = true;
            } else if (fruit instanceof Orange) {
                if (orangeExpDate.after(fruit.getPickDate())) exp = true;
            }
            if (exp) System.out.println(fruit + "  过期! ");
        }
        System.out.println("-------");
    }
   
}

如果此时再添加一个操作:找出新品上市的水果。

// 新品上市 苹果为2023-06-20 后采摘, 橘子为2023-06-21后采摘
    public static void newArrival(FruitShop shop) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date appleNewDate = simpleDateFormat.parse("2023-06-20");
        Date orangeNewDate = simpleDateFormat.parse("2023-06-21");
        for (Fruit fruit : shop.getFruitList()) {
            boolean isNew = false;
            if (fruit instanceof Apple) {
                if(appleNewDate.before(fruit.getPickDate())) isNew = true;
            } else if (fruit instanceof  Orange) {
                if(orangeNewDate.before(fruit.getPickDate())) isNew= true;
            }
            if (isNew) System.out.println(fruit.getName() + " 新品上市");
        }
    }

上述代码中,FruitMarket为应付新增操作,增加了相应的方法来满足要求,但这样破坏了FruitMarket的结构。

访问者模式

本质是将数据结构和数据操作分离,通过定义一个访问者对象,实现对数据结构中各个元素的访问和处理,从而达到解耦和灵活性的目的。

设计模式—访问者模式_第1张图片

图 访问者UML

Visitor 抽象访问者

为ObjectStructure对象结构中的每一个Element都声明一个Visit操作。

ConcreteVisitor具体访问者

Visitor的实现,实现要真正被添加到对象结构中的功能。

ObjectStructure 对象结构

通常包含多个被访问的对象,可以是一个复合或是一个集合。

Element 抽象元素

为Visitor声明一个accept方法,实现元素与访问者的绑定。

ConcreteElement 具体元素

对象结构体中具体的对象,是被访问的对象。

/**
 * 水果元素抽象类
 */
public abstract class FruitElement {

    private String name;
    private Date pickDate;

    public FruitElement(String name, Date pickDate) {
        this.name = name;
        this.pickDate = pickDate;
    }

    public String getName() {
        return name;
    }

    public Date getPickDate() {
        return pickDate;
    }

    public abstract void accept(Visitor visitor);

    @Override
    public String toString() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        return "{" +
                "name='" + name + '\'' +
                ", 采摘日期=" + simpleDateFormat.format(pickDate) +
                '}';
    }

}
public class AppleElement extends FruitElement{

    public AppleElement(String name, Date pickDate) {
        super(name, pickDate);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

public class OrangeElement extends FruitElement{

    public OrangeElement(String name, Date pickDate) {
        super(name, pickDate);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

}
/**
 * 访问者
 */
public interface Visitor {

    void visit(AppleElement apple);

    void visit(OrangeElement orange);

}

/**
 * 政府检查部门
 */
public class DevelopmentVisitor implements Visitor{

    private final int appleExpDay;

    private final int orangeExpDay;

    public DevelopmentVisitor(String name, int appleExpDay, int orangeExpDay) {
        this.appleExpDay = appleExpDay;
        this.orangeExpDay = orangeExpDay;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        System.out.println(name + " 检查过期水果, 苹果过期天数:" + appleExpDay + ",橘子过期天数:" +
                orangeExpDay + " 检查时间:" + dateFormat.format(new Date()));
    }

    @Override
    public void visit(AppleElement apple) {
        expCheck(apple, appleExpDay);
    }

    @Override
    public void visit(OrangeElement orange) {
        expCheck(orange,orangeExpDay);
    }

    private void expCheck(FruitElement fruit,int expDay) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE,-appleExpDay);
        Date appleExpDate = calendar.getTime();
        if (appleExpDate.after(fruit.getPickDate())) {
            System.out.println(fruit.getName() + " 过期!");
        }
    }
}


/**
 * 新品上市检查
 */
public class NewArrivalVisitor implements Visitor {

    private final Date appleNewDate;

    private final Date orangeNewDate;

    public NewArrivalVisitor(Date appleNewDate, Date orangeNewDate) {
        this.appleNewDate = appleNewDate;
        this.orangeNewDate = orangeNewDate;
        System.out.println("新品上市检查");
    }

    @Override
    public void visit(AppleElement apple) {
        if (appleNewDate.before(apple.getPickDate())) {
            System.out.println(apple.getName() + " 新品上市");
        }
    }

    @Override
    public void visit(OrangeElement orange) {
        if (orangeNewDate.before(orange.getPickDate())) {
            System.out.println(orange.getName() + " 新品上市");
        }
    }
}
/**
 * 水果店结构
 */
public class FruitShopStructure {

    private final List fruitList = new ArrayList<>();

    public List getFruitList() {
        return fruitList;
    }

}
public class FruitMarket2 {

    public static void main(String[] args) throws ParseException {
        //新开一家水果店
        FruitShopStructure shop = new FruitShopStructure();
        purchaseFruit(shop);

        System.out.println("------------------------------");
        //市场监督局来检查是否存在过期水果
        DevelopmentVisitor devVisitor1 = new DevelopmentVisitor("市场监督局", 5, 10);
        for (FruitElement element : shop.getFruitList()) {
            element.accept(devVisitor1);
        }
        System.out.println("------------------------------");
        //农业局来检查是否存在过期水果
        DevelopmentVisitor devVisitor2 = new DevelopmentVisitor("农业局", 2, 4);
        for (FruitElement element : shop.getFruitList()) {
            element.accept(devVisitor2);
        }
        System.out.println("------------------------------");
        //检查新品上市
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        NewArrivalVisitor newArrivalVisitor = new NewArrivalVisitor(simpleDateFormat.parse("2023-06-20"), simpleDateFormat.parse("2023-06-21"));
        for (FruitElement element : shop.getFruitList()) {
            element.accept(newArrivalVisitor);
        }
    }

    // 采购水果
    public static void purchaseFruit(FruitShopStructure shop) throws ParseException {
        List fruitList = shop.getFruitList();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        fruitList.add(new AppleElement("富士康",simpleDateFormat.parse("2023-06-21 12:00")));
        fruitList.add(new OrangeElement("赣南脐橙", simpleDateFormat.parse("2023-05-25 18:00")));
        fruitList.add(new OrangeElement("韶关帝王橘", simpleDateFormat.parse("2023-6-18 21:00")));
        fruitList.add(new AppleElement("王掌柜",simpleDateFormat.parse("2023-06-11 12:00")));
    }
}

设计模式—访问者模式_第2张图片

图 运行结果 

以上代码是用访问者模式实现文章开头处的需求。

优点

  1. 好的扩展性,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 好的复用性,可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
  3. 分离无关行为,通过访问者来分离无关的行为,把相关行为封装在一起,构成一个访问者,这样每个访问者的功能都比较单一。

缺点

1)对象结构变化很困难,不适用于对象结构中的类经常变化的情况,当对象结构发生改变时,访问者的接口及实现都要做相应改变。

2)破坏封装,访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性。

应用场景

1)需要对一个复杂的数据结构进行操作,并且这些操作可能需要根据不同的元素类型进行变化。

2)当数据结构中的元素种类相对稳定,但可能需要新增一些新的操作时。

你可能感兴趣的:(设计模式,访问者模式)