小唐说设计模式————策略模式篇

策略模式适用于什么样子的场景呢?

    当我们的代码中出现了一连串的if…else…或者是switch…case…语句时,我们的代码体就会很长很臃肿,阅读性大大下降,此时可采用策略模式进行重构。

原理

    策略模式利用的是面向对象语言的三个特性,尤其是继承和多态。首先让多个类继承同一个抽象的父类或者是实现一个接口,在抽象类或者接口中声明我们要实现的抽象方法,在子类或者实现类中写出不同的方法实现。根据多态性,创建一个父类类型的引用,让其指向子类的对象,让根据对象的不同,执行各个同名但不同体的方法。

示例讲解

    以一个在线租赁商店demo为例:

    影片类,具有标题,价格码(对应的是影片的类型):

public class Movie {
    public static final int CHILDRENS = 2;
    public static final int REGULAR = 0;
    public static final int NEW_RELEASE = 1;

    private String title;
    private int priceCode;

    public Movie(String title, int priceCode) {
        this.title = title;
        this.priceCode = priceCode;
    }

    public int getPriceCode() {
        return priceCode;
    }

    public void setPriceCode(int priceCode) {
        this.priceCode = priceCode;
    }

    public String getTitle() {
        return title;
    }
}

    租赁单类,包含一部电影和租赁的日期,同时还包含一个计算租赁金的方法:

public class Rental {
    private Movie movie;
    private int dayRented;

    public Rental(Movie movie, int dayRented) {
        this.movie = movie;
        this.dayRented = dayRented;
    }

    public Movie getMovie() {
        return movie;
    }

    public int getDayRented() {
        return dayRented;
    }

    public double getAmount(){
        double thisAmount = 0d;
        switch (movie.getPriceCode()) {
            case Movie.REGULAR:
                thisAmount += 2;
                if (this.getDayRented() > 2) {
                    thisAmount += (this.getDayRented() - 2) * 1.5;
                }
                break;
            case Movie.NEW_RELEASE:
                thisAmount += this.getDayRented() * 3;
                break;
            case Movie.CHILDRENS:
                thisAmount += 1.5;
                if (this.getDayRented() > 3) {
                    thisAmount += (this.getDayRented() - 3) * 1.5;
                }
                break;
        }
        return thisAmount;
    }
}

    顾客类,包含姓名、租赁的影片、总的租金以及积分点,和一个打印所有租赁信息的方法printStatement:

public class Customer {
    private String name;
    double totalAmount;
    int frequentRenterPoints;
    private List<Rental> rentals = new ArrayList<>();

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

    public void addRental(Rental arg) {
        rentals.add(arg);
    }

    public String getName() {
        return name;
    }

    public void printHeader(){
        String header = "Rental Record for " + getName() + "\n";
        System.out.print(header);
    }

    public void printBody(){
        String body = "";
        for (Rental each : this.rentals) {
            frequentRenterPoints++;
            if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
                frequentRenterPoints++;
            }
            body += "\t" + each.getMovie().getTitle() + "\t" + each.getAmount() + "\n";
            totalAmount += each.getAmount();
        }
        System.out.print(body);
    }

    public void printTail(){
        String tail = "";
        tail += "Amount owed is " + totalAmount + "\n";
        tail += "You earned " + frequentRenterPoints + " frequent renter points";
        System.out.print(tail);
    }

    public void printStatement(){
        printHeader();
        printBody();
        printTail();
    }
}

    主类,用于运行和测试:

public class Main {

    public static void main(String[] args) {
        Movie movie = new Movie("泰坦尼克号",Movie.REGULAR);
        Rental rental = new Rental(movie,3);
	    Customer customer = new Customer("Lily");
	    customer.addRental(rental);
	    customer.printStatement();
    }
}

    运行结果:

Rental Record for Lily
	泰坦尼克号	3.5
Amount owed is 3.5
You earned 1 frequent renter points

    代码其实不难理解(因为我觉得方法命名还不错…),根据用户租赁的影片打印出用户消费的票据,每部影片的租金根据电影的标签和租赁时长计算,还可以累积积分点。

    一眼看过去其实还是有几个比较明显的bad code smell,我们暂且先选择性忽视它们…

    Rental类的getAmount()方法属于典型的长方法,又臭又长,大部分的原因是因为这个switch语句:
``
switch (movie.getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (this.getDayRented() > 2) {
thisAmount += (this.getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += this.getDayRented() * 3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if (this.getDayRented() > 3) {
thisAmount += (this.getDayRented() - 3) * 1.5;
}
break;
}
```
    接下来我们尝试消除这段长代码!!!

    根据策略模式的思想,我们可以让三个不同的类去继承Rental类,在Rental类中添加一个抽象方法,让各个子类去给出不同的实现,这样就可以取代不同的case。

abstract public class Rental {
    private Movie movie;
    private int dayRented;

    public Rental(Movie movie, int dayRented) {
        this.movie = movie;
        this.dayRented = dayRented;
    }

    public Movie getMovie() {
        return movie;
    }

    public int getDayRented() {
        return dayRented;
    }

    public double getAmount(){
        double thisAmount = 0d;
        return countThisAmount(thisAmount);
    }

    abstract public double countThisAmount(double thisAmount);
}

    之前的判断逻辑,全都被放到子类的继承的抽象方法去实现:

public class NewReleaseRental extends Rental {
    public NewReleaseRental(Movie movie, int dayRented) {
        super(movie, dayRented);
    }

    @Override
    public double countThisAmount(double thisAmount) {
        thisAmount += this.getDayRented() * 3;
        return thisAmount;
    }
}
public class RegularRental extends Rental {
        public RegularRental(Movie movie, int dayRented) {
            super(movie, dayRented);
        }

        @Override
        public double countThisAmount(double thisAmount) {
            thisAmount += 2;
            if (this.getDayRented() > 2) {
                thisAmount += (this.getDayRented() - 2) * 1.5;
            }
            return thisAmount;
        }
}
public class ChildrenRental extends Rental{
    public ChildrenRental(Movie movie, int dayRented) {
        super(movie, dayRented);
    }

    @Override
    public double countThisAmount(double thisAmount) {
        thisAmount += 1.5;
        if (this.getDayRented() > 3) {
            thisAmount += (this.getDayRented() - 3) * 1.5;
        }
        return thisAmount;
    }
}

    而原来的长方法,变得非常简洁,大大增强了可读性。

public double getAmount(){
        double thisAmount = 0d;
        return countThisAmount(thisAmount);
 }	

适用场景

    策略模式正如其名字一样,提供不同的策略,用户只需要决定用哪个算法,算法内部被系统封装。因此,策略模式多用在算法决策系统中,对不同的输入做不同处理:

  • 以不同的格式保存文件;

  • 以不同的算法压缩文件;

  • 以不同的算法截获图象;

总结

    策略模式在重构代码中帮助很大,往往能消除长方法,大大提高代码的可读性。同时,经过策略模式重构后的代码更易于扩展,一旦有新的条件出现,直接构建一个新的策略就可以,不会影响其他代码。

关于重构代码的思考

    策略模式、模板模式、提取方法等都可以去重构代码,好处在于,更易于后期的维护和继续开发,利于代码重用,代码更具有可读性。但是重构也会带来一些问题,频繁的函数调用会更耗费内存,过度地重构浪费开发时间,说不定还会减少可读性。何解呢?业务需求必定是其一(emmmmmm),还是见仁见智吧,每个人都有自己的理解和行为习惯,只要你觉得ok,你的partners也ok,就ok(o)/~

上一篇:day3


下一篇:python-Pygame.movi​​e丢失