需求:店铺采购了一批水果(苹果及橘子),现在市场监督局来店里检查过期的水果。
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的结构。
本质是将数据结构和数据操作分离,通过定义一个访问者对象,实现对数据结构中各个元素的访问和处理,从而达到解耦和灵活性的目的。
图 访问者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")));
}
}
图 运行结果
以上代码是用访问者模式实现文章开头处的需求。
1)对象结构变化很困难,不适用于对象结构中的类经常变化的情况,当对象结构发生改变时,访问者的接口及实现都要做相应改变。
2)破坏封装,访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性。
1)需要对一个复杂的数据结构进行操作,并且这些操作可能需要根据不同的元素类型进行变化。
2)当数据结构中的元素种类相对稳定,但可能需要新增一些新的操作时。