设计模式(1)---策略模式
策略模式 Strategy(行为型模式)
1.概述
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
2.问题
如何让算法和对象分开来,降低他们之间的耦合度,使得算法可以独立于使用它的客户而变化?
3.解决方案
策略模式:它定义了一系列算法,把每一个算法封装起来,让它们之间可以相互替换,本模式使得算法可独立于使用它的客户而变化。
4.结构
5.例子
商场收银软件:营业员根据顾客所购买商品的单价和数量,计算总价。商场可能会有促销活动,比如全场打8折,全场打5折,买200返100等。
实现方式一:可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的打折算法;当然也可以将这些打折算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的打折算法,需要修改封装算法类的源代码。该类代码将较复杂,维护较为困难。
如果需要修改或者新增算法,需要修改原有的类,违反了 开闭原则 ,系统的灵活性和可扩展性差。
算法的复用性较差,无法重用某些算法。
实现方式二:使用策略模式。代码如下:
1 package strategy; 2 /* 3 * 策略类 4 */ 5 public interface Strategy { 6 double Promote(double money); 7 }
1 package strategy; 2 /* 3 * 不使用打折的具体策略类 4 */ 5 public class CashNormalImpl implements Strategy { 6 7 public CashNormalImpl() { 8 9 } 10 11 @Override 12 public double Promote(double money) { 13 14 return money; 15 } 16 17 }
1 package strategy; 2 /* 3 * 使用按比例打折的具体策略类 4 */ 5 public class CashRebateImpl implements Strategy { 6 7 private double moneyRebate ; 8 public CashRebateImpl(double moneyRebate) { 9 this.moneyRebate = moneyRebate ; 10 } 11 12 @Override 13 public double Promote(double money) { 14 15 return money*moneyRebate; 16 } 17 18 }
1 package strategy; 2 /* 3 * 使用返利打折方式的具体策略类 4 */ 5 public class CashReturnImpl implements Strategy { 6 7 private double moneyCondition ; 8 private double moneyReturn ; 9 10 public CashReturnImpl(double moneyCondition , double moneyReturn) { 11 this.moneyCondition = moneyCondition ; 12 this.moneyReturn = moneyReturn ; 13 } 14 15 @Override 16 public double Promote(double money) { 17 double result = 0.0d; 18 if (money >= moneyCondition){ 19 result = money - Math.floor(money/moneyCondition) * moneyReturn ; 20 } 21 return result; 22 } 23 24 }
1 package strategy; 2 /* 3 * Context类 4 */ 5 public class CashContext { 6 7 //Strategy对象的引用 8 private Strategy contreteStrategy = null ; 9 10 public CashContext(String str) { 11 if (str.equals("正常收费")){ 12 contreteStrategy = new CashNormalImpl() ; 13 }else if (str.equals("打八折")){ 14 contreteStrategy = new CashRebateImpl(0.8) ; 15 }else if (str.equals("打五折")){ 16 contreteStrategy = new CashRebateImpl(0.5) ; 17 }else if (str.equals("满200返100")){ 18 contreteStrategy = new CashReturnImpl(200, 100) ; 19 } 20 21 } 22 23 public double getResult(double money){ 24 //当添加具体策略类时,这条代码也不用变,利用的多态的特点 25 return contreteStrategy.Promote(money); 26 } 27 28 }
界面(部分代码):
1 btnOK.addActionListener(new ActionListener() { 2 @Override 3 public void actionPerformed(ActionEvent e) { 4 CashContext context = new CashContext(boxStrategy.getSelectedItem().toString()) ; 5 double price = Double.parseDouble(textPrice.getText()) ; 6 double num = Double.parseDouble(textNum.getText()) ; 7 double total = context.getResult(price*num); 8 sum += total ; 9 textSum.setText(String.valueOf(sum)); 11 textNum.setText(""); 12 textPrice.setText(""); 13 14 } 15 });
界面(完整代码):
1 package strategy; 2 3 import java.awt.Font; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.text.SimpleDateFormat; 7 import java.util.Date; 8 9 import javax.swing.JButton; 10 import javax.swing.JComboBox; 11 import javax.swing.JFrame; 12 import javax.swing.JLabel; 13 import javax.swing.JOptionPane; 14 import javax.swing.JPanel; 15 import javax.swing.JScrollPane; 16 import javax.swing.JTextArea; 17 import javax.swing.JTextField; 18 19 20 21 public class CashFrame extends JFrame { 22 23 private JPanel contentPane = new JPanel() ;//主面板 24 25 26 27 private JLabel labPrice ; //单价提示文本 28 private JTextField textPrice ; //单价设置框 29 private JButton btnOK ; //确定 30 31 private JLabel labNum ; //数量设置提示文本 32 private JTextField textNum ; //数量设置框 33 private JButton btnReset ; //重置 34 35 private JLabel labStrategy ; //策略设置提示文本 36 private JComboBox boxStrategy ; //策略设置框 37 38 private JScrollPane scrollPane ; // 滚动面板 39 private JTextArea textArea ; //信息显示 40 41 private JLabel labSum ; //总价提示文本 42 private JLabel textSum ; //总价设置框 43 private double sum = 0.0D ;//总价 44 45 private static CashFrame instance ; 46 47 private CashFrame(){ 48 init() ; 49 UiUtil.setFrameCenter(this); 50 this.setTitle("商场收银系统") ; 51 this.setResizable(false); 52 this.setVisible(true); 53 } 54 55 public synchronized static CashFrame getInstance(){ 56 if (instance == null){ 57 instance = new CashFrame(); 58 } 59 return instance ; 60 } 61 62 public void init(){ 63 this.setBounds(200,100,450,450); 64 this.setDefaultCloseOperation(EXIT_ON_CLOSE); 65 this.setContentPane(contentPane); 66 67 contentPane.setLayout(null); 68 /*----------------------------*/ 69 labPrice = new JLabel("单价: ") ; 70 labPrice.setBounds(40,20,80,30); 71 contentPane.add(labPrice) ; 72 73 textPrice = new JTextField() ; 74 textPrice.setBounds(90,20,150,30); 75 contentPane.add(textPrice) ; 76 77 btnOK = new JButton("确定") ; 78 btnOK.setBounds(290,20,80,30); 79 contentPane.add(btnOK) ; 80 81 82 /*----------------------------*/ 83 labNum = new JLabel("数量: ") ; 84 labNum.setBounds(40,60,80,30); 85 contentPane.add(labNum) ; 86 87 textNum = new JTextField() ; 88 textNum.setBounds(90,60,150,30); 89 contentPane.add(textNum) ; 90 91 btnReset = new JButton("重置") ; 92 btnReset.setBounds(290,60,80,30); 93 contentPane.add(btnReset) ; 94 95 /*----------------------------*/ 96 labStrategy = new JLabel("促销方式: ") ; 97 labStrategy.setBounds(15,100,80,30); 98 contentPane.add(labStrategy) ; 99 100 boxStrategy = new JComboBox() ; 101 boxStrategy.setBounds(90,100,150,30); 102 contentPane.add(boxStrategy) ; 103 boxStrategy.addItem("正常收费"); 104 boxStrategy.addItem("打八折"); 105 boxStrategy.addItem("打五折"); 106 boxStrategy.addItem("满200返100"); 107 108 /*----------------------------*/ 109 textArea = new JTextArea() ; 110 textArea.setBounds(0,0,400,200); 111 textArea.setEditable(false); 112 textArea.setFont(new Font("宋体", Font.PLAIN, 15)); 113 114 scrollPane = new JScrollPane(textArea) ; 115 scrollPane.setBounds(20,145,400,200); 116 scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 117 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 118 119 contentPane.add(scrollPane) ; 120 /*----------------------------*/ 121 122 labSum = new JLabel("总价: ") ; 123 labSum.setBounds(40,370,80,30); 124 contentPane.add(labSum) ; 125 126 textSum = new JLabel() ; 127 textSum.setBounds(90,370,150,30); 128 contentPane.add(textSum) ; 129 130 btnOK.addActionListener(new ActionListener() { 131 @Override 132 public void actionPerformed(ActionEvent e) { 133 CashContext context = new CashContext(boxStrategy.getSelectedItem().toString()) ; 134 double price = Double.parseDouble(textPrice.getText()) ; 135 double num = Double.parseDouble(textNum.getText()) ; 136 double total = context.getResult(price*num); 137 sum += total ; 138 showInfo("单价:"+textPrice.getText()+" 数量:"+textNum.getText()+" 方式:"+boxStrategy.getSelectedItem().toString()+" 合计:"+String.valueOf(total)); 139 textSum.setText(String.valueOf(sum)); 140 textNum.setText(""); 141 textPrice.setText(""); 142 143 } 144 }); 145 146 btnReset.addActionListener(new ActionListener() { 147 @Override 148 public void actionPerformed(ActionEvent e) { 149 textNum.setText(""); 150 textPrice.setText(""); 151 textSum.setText(""); 152 textArea.setText(""); 153 154 } 155 }); 156 157 } 158 159 public void showInfo(String info){ 160 textArea.append(info+"\r\n"); 161 textArea.setCaretPosition(textArea.getText().length()) ;//光标定位到最后一行 可以让滚动条保持在最下方 162 } 163 164 public static void main(String[] args) { 165 instance = new CashFrame() ; 166 167 } 168 169 }
6.适用性
当存在以下情况时使用Strategy模式
1)• 一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
7.优点
(1)所有这些打折算法完成的都是相同的工作,只是实现不同,策略模式可以以相同的方式调用所有的算法,降低了对象与算法的耦合。
(2)Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
(3)简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
(4)消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
8.总结
1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
9.策略模式在java容器布局管理中的应用
Container 相当于环境类
LayoutManager 相当于抽象策略类
而具体策略类是LayoutManager的子类,也就是各种具体的布局类,它们封装了不同的布局方式。
1 public class Container extends Component { 2 LayoutManager layoutMgr; 3 4 public void setLayout(LayoutManager mgr) { 5 layoutMgr = mgr; 6 if (valid) { 7 invalidate(); 8 } 9 } 10 11 12 }