透過源碼學習設計模式6—策略模式與Comparator

  • 2019 年 10 月 6 日
  • 筆記

簡介:

定義演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶端。它首先定義不同的演算法策略,然後客戶端把演算法策略作為它的一個參數。使用這種模式的一個不錯的例子是Collection.sort()方法了,它使用Comparator對象作為參數。根據Comparator介面不同實現,對象會被不同的方法排序。

角色:

環境(Context):用來操作策略的上下文環境,屏蔽高層模組(客戶端)對策略,演算法的直接訪問,封裝可能存在的變化;

抽象策略角色(Strategy):定義了一個公共介面,各種不同的演算法以不同的方式實現這個介面,環境角色使用這個介面調用不同的演算法,一般使用介面或抽象類實現

具體策略角色(ConcreteStrategy):實現了抽象策略定義的介面,提供具體的演算法實現。

原則:

1、找出應用中可能需要變化之處,把他們獨立出來。

2、針對介面編程,而不是針對實現

3、多用組合,少用繼承

一般使用場景:

當系統能在幾種演算法中快速地切換,或系統中有一些類,它們僅行為不同時,或系統中存在多重條件選擇語句時,可以考慮採用策略模式。

優缺點:

優點:

1、演算法可以自由切換

2、避免使用多重條件判斷

3、擴展性良好

缺點:

策略類數量增多,所有的策略類都需要對外暴露(不過可以通過其他如工廠方法模式修復這個缺陷)

JDK示例:

java.util.Collections方法:

sort(List<T> list, Comparator<? super T> c)

參數:

list – 要排序的列表。

c – 確定列表順序的比較器。null 值指示應該使用元素的 自然順序。

根據指定Comparator產生的順序對指定列表進行排序。此列表內的所有元素都必須能夠使用指定Comparator相互比較。sort()方法是常用的策略設計模式示例之一,它以Comparator作為參數,我們可以為Comparator介面提供不同的實現。現在,Collections.sort()方法將根據傳遞給它的比較器實現對象排序。比如根據體重或大小來對Employee進行排序,您可以簡單地創建一個Comparator來實現。

我們先來看下可與之對比的Comparable介面, 它為類的對象定義了默認順序。這種默認順序也稱為對象的自然順序。

class Employee implements Comparable<Employee> {        private int id;        private String name;        private double salary;        private LocalDate joiningDate;          public Employee(int id, String name, double salary, LocalDate joiningDate) {            this.id = id;            this.name = name;            this.salary = salary;            this.joiningDate = joiningDate;        }          public int getId() {            return id;        }          public void setId(int id) {            this.id = id;        }          public String getName() {            return name;        }          public void setName(String name) {            this.name = name;        }            public double getSalary() {            return salary;        }            public void setSalary(double salary) {            this.salary = salary;        }          public LocalDate getJoiningDate() {            return joiningDate;        }          public void setJoiningDate(LocalDate joiningDate) {            this.joiningDate = joiningDate;        }            // 根據ID進行比較        /**         * @param   anotherEmployee - The Employee to be compared.         * @return  A negative integer, zero, or a positive integer as this employee         *          is less than, equal to, or greater than the supplied employee object.        */        @Override        public int compareTo(Employee anotherEmployee) {            return this.getId() - anotherEmployee.getId();        }          // Two employees are equal if their IDs are equal        @Override        public boolean equals(Object o) {            if (this == o) return true;            if (o == null || getClass() != o.getClass()) return false;            Employee employee = (Employee) o;            return id == employee.id;        }          @Override        public int hashCode() {            return Objects.hash(id);        }          @Override        public String toString() {            return "Employee{" +                    "id=" + id +                    ", name='" + name + ''' +                    ", salary=" + salary +                    ", joiningDate=" + joiningDate +                    '}';        }    }

但是,如果您因為某個需求更改默認順序呢?例如,如果您想根據前面示例中的Employee對象的名稱而不是id對其進行排序,該怎麼辦?

你不能更改compareTo()函數的實現,因為不單單是你那塊的特定需求, 它會影響所有地方的順序。

此外,如果您正在處理預定義的Java類或在第三方庫中定義的類,則無法更改默認順序。例如,字元串對象的默認順序是按字母順序排列。但是如果你想根據它們的長度來排序呢?

對於這種情況,Java提供的正是Comparator介面。您可以定義一個Comparator並將其傳遞給像Collections.sort() 或 Arrays.sort()的排序函數。根據Comparator定義的順序對對象排序。

public interface Comparator<T> {        int compare(T o1, T o2);    }        public class ComparatorExample {        public static void main(String[] args) {            List<Employee> employees = new ArrayList<>();              employees.add(new Employee(1010, "Rajeev", 100000.00, LocalDate.of(2010, 7, 10)));            employees.add(new Employee(1004, "Chris", 95000.50, LocalDate.of(2017, 3, 19)));            employees.add(new Employee(1015, "David", 134000.00, LocalDate.of(2017, 9, 28)));            employees.add(new Employee(1009, "Steve", 100000.00, LocalDate.of(2016, 5, 18)));                System.out.println("Employees : " + employees);              // 根據名稱排序            Comparator<Employee> employeeNameComparator = new Comparator<Employee>() {                @Override                public int compare(Employee e1, Employee e2) {                    return e1.getName().compareTo(e2.getName());                }            };            /*            上面的Comparator可以用如下的lambda表達式寫 =>            employeeNameComparator = (e1, e2) -> e1.getName().compareTo(e2.getName());            Java 8 Comparator默認方法甚至可以寫的更簡單            employeeNameComparator = [Comparator.comparing(Employee::getName](http://Comparator.comparing(Employee::getName))            */            Collections.sort(employees, employeeNameComparator);            System.out.println("nEmployees (Sorted by Name) : " + employees);            // 根據收入排序            Comparator<Employee> employeeSalaryComparator = new Comparator<Employee>() {                @Override                public int compare(Employee e1, Employee e2) {                    if(e1.getSalary() < e2.getSalary()) {                        return -1;                    } else if (e1.getSalary() > e2.getSalary()) {                        return 1;                    } else {                        return 0;                    }                }            };              Collections.sort(employees, employeeSalaryComparator);            System.out.println("nEmployees (Sorted by Salary) : " + employees);              //根據入職時間排序            Comparator<Employee> employeeJoiningDateComparator = new Comparator<Employee>() {                @Override                public int compare(Employee e1, Employee e2) {                    return e1.getJoiningDate().compareTo(e2.getJoiningDate());                }            };              Collections.sort(employees, employeeJoiningDateComparator);            System.out.println("nEmployees (Sorted by JoiningDate) : " + employees);        }    }

與狀態模式區別