从问题角度来思考设计模式(3) – 行为编

目录

  1. 生成编
  2. 结构编
  3. 行为编

让数据和处理逻辑分离

  • 改造前
public class FooAmusementPark {

    private FooZoo zoo;
    private FooAquarium aquarium;

    public void enjoy(FooFamily family) {
        zoo.enjoy();
    }

    public void enjoy(FooCouple couple) {
        aquarium.enjoy();
    }
}

Family,Couple以外类型的用户增加时,FooAmusementPark类则必须扩充

  • 改造后
    Visitor
public class FooAmusementPark {

    private FooZoo zoo;
    private FooAquarium aquarium;

    public void accept(FooVisitor visitor) {
        visitor.visit(this);
    }
}

public class FooFamily extends FooVisitor {

    @Override
    public void visit(FooAmusementPark park) {
        park.getZoo().enjoy();
    }
}

public class FooCouple extends FooVisitor {

    @Override
    public void visit(FooAmusementPark park) {
        park.getAquarium().enjoy();
    }
}

改造后,Family,Couple以外类型的用户增加时,AmusementPark不需要更改,通过其中的visit方法就能满足需求。
当数据结构不经常变化的,是使用Visitor模式的考虑点。

需求多变时,如何分离处理逻辑

  • 改造前
public class FooController {

    private FooLatLngToPlaceAPI api;

    public String getFormattedAddress(double latitude, double longitude) {
        FooPlace place = api.getPlace(latitude, longitude);

        return place.getPostalCode()
                + System.lineSeparator()
                + place.getProvince()
                + " "
                + place.getCity();
    }
}

假设返回的数据邮编 换行 省 市格式。下一次迭代可能又换成邮编 省-市或者其它。当多个地方返回地址数据都希望保持同一的格式时,一个个去拼装字符串就显得有些繁琐了。

  • 改造后
    Strategy
public class FooController {

    private FooLatLngToPlaceAPI api;
    private FooAddressFormatter formatter = new FooAddressFormatter();

    public String getFormattedAddress(double latitude, double longitude) {
        FooPlace place = api.getPlace(latitude, longitude);

        return formatter.format(place);
    }
}

public class FooAddressFormatter {

    public String format(FooPlace place) {
        return place.getPostalCode()
                + System.lineSeparator()
                + place.getProvince()
                + " "
                + place.getCity();
    }
}

将拼装返回数据的逻辑单独封装成一个类。
当需求变化时,也只需要改动这个类的逻辑即可,而又能保持全局格式统一。
当不想创建一个新类时,也可以这样子写

public class FooController {

    private FooLatLngToPlaceAPI api;

    private static final Function<FooPlace, String> FORMAT_ADDRESS =
            place -> place.getPostalCode()
                    + System.lineSeparator()
                    + place.getPrefecture()
                    + " "
                    + place.getCity();

    public String getFormattedAddress(double latitude, double longitude) {
        FooPlace place = api.getPlace(latitude, longitude);

        return FORMAT_ADDRESS.apply(place);
    }
}

根据条件区分近似的处理逻辑

  • 改造前
public String getFormattedText(String text, FormatType type) {
    switch (type) {
        case BOLD:
            return "**" + text + "**";

        case ITALIC:
            return "*"  + text + "*";

        default:
            return text;
    }
}

type值变多时,switch里的条件判断及代码块就变得越来越多了,不利代码维护。

  • 改造后
    Strategy + Factory
public class FooFormatterFactory {

    private FooFormatterFactory() {}

    public static FooFormatter create(FormatType type) {
        switch (type) {
            case BOLD:
                return new FooBoldFormatter();

            case ITALIC:
                return new FooItalicFormatter();

            default:
                return new FooFormatter() {
                    @Override
                    public String format(String text) {
                        return text;
                    }
                }
        }
    }
}

public class FooBoldFormatter extends FooFormatter {

    @Override
    public String format(String text) {
        return "**" + text + "**";
    }
}

public class FooItalicFormatter extends FooFormatter {

    @Override
    public String format(String text) {
        return "*" + text + "*";
    }
}
public String getFormattedText(String text, FormatType type) {
  return FooFormatterFactory.create(type).format(text);
}

改造后,当type值变多时,getFormattedText方法内容则不受影响。
通过StrategyFactory模式结合使用的例子挺多的。
当不想创建新类的时,可以参考下面代码,用函数式接口实现

public class FooFormatterFactory {

    private FooFormatterFactory() {}

    public static UnaryOperator<String> create(FormatType type) {
        switch (type) {
            case BOLD:
                return text -> "**" + text + "**";

            case ITALIC:
                return text -> "*"  + text + "*";

            default:
                return text -> text;
        }
    }
}
public String getFormattedText(String text, FormatType type) {
    return FooFormatterFactory.create(type).apply(text);
}

如何利用上一次处理的结果

  • 改造前
public class FooController {

    private int nextId;

    public FooResponse get(FooRequest request) {
        FooResponse response = getResponse(request);
        nextId = response.getNextId();
        return response;
    }

    public FooResponse getNext() {
        FooRequest request = new FooRequest(nextId);
        return get(request);
    }

    private FooResponse getResponse(FooRequest request) {
        // 处理
        return // 结果
    }
}

getNext()方法中把包含上一次的结果的nextId做为参数生成request实例。当request生成时所需要的参数的数量或类型改变时,FooController中所有的值,逻辑等都需要变更。

  • 改造后
    Memento
public class FooMemento {

    private int nextId;

    public void update(FooResponse response) {
        this.nextId = response.getNextId();
    }

    public FooRequest createNextRequest() {
        return new FooRequest(nextId);
    }
}
public class FooController {

    private FooMemento memento = new FooMemento();

    public FooResponse get(FooRequest request) {
        FooResponse response = getResponse(request);
        memento.update(response);
        return response;
    }

    public FooResponse getNext() {
        return get(memento.createNextRequest());
    }

    private FooResponse getResponse(FooRequest request) {
        // 处理
        return // 结果
    }
}

通过Memento模式来记忆上次数据。
当新request生成时所需要的参数变化时,只需要在FooMemento中做修改。
Memento还经常用到单机游戏里,如小时候玩的存档型游戏,存档就是记录某进度下的角色们的各种状态,位置,关卡等信息。当读取存档时,其实也就是读取里面的状态数据,将程序恢复到该时刻。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注