zoukankan      html  css  js  c++  java
  • 大话设计模式--第二章 策略设计模式

    策略设计模式

    现在有一个需求: 给商场做一个收银软件. 营业员根据客户购买的产品的单价和数量, 向客户打印小票。

    这个实现很简单. 一个类就可以搞定:

    package com.designModel.chaper2_strategeModel.step1;
    
    import java.util.Scanner;
    
    /**
     * cash收银
     * @author samsung
     *
     */
    public class Cash {
        public String list = "";
        public Double totalPrice = 0.00;
        
        public void buttonOK(){
            
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣:");
            String zhekou = scan.nextLine();
            
            Double xiaoji = Double.parseDouble(price) * Integer.parseInt(num) * Double.parseDouble(zhekou)/10;
            list += "单价:" + price +", 数量: "+ num +",折扣: " + zhekou + "
    ";
            totalPrice += xiaoji;
            
            
        }
        
        public static void main(String[] args) {
            Cash cash = new Cash();
            boolean flag = true;
            while(flag) {
                cash.buttonOK();
                if(cash.totalPrice > 10){
                    flag = false;
                }
            }
            
            System.out.println("========================");
            System.out.println("清单:
     " + cash.list);
            System.out.println("总价: "+ cash.totalPrice);
        }
        
    }

    但是: 用面向对象的角度思考. 这个类将前端输入和业务逻辑混合在一块了. 不利于维护, 扩展, 复用, 也不灵活. 

    假如: 现在商场搞活动, 所有商品打折, 7折, 

    过一段时间, 商场又搞活动, 所有商品打5折

    国庆节, 商品满200减50.

     

    如果按照上面的方式来写代码, 那么每次都要写一遍, 如何将其复用起来呢? 并且每次增加新的活动的时候, 又不会影响到原来的活动. 

    对了, 简单工厂设计模式, 我们之前刚刚学过的. 下面看看简单工厂设计模式的UML图:

    首先, 有一个工厂类, 在这个工厂类里面, 根据类型, 依赖于不同的现金收费方式. 具体代码如下:

    首先与一个抽象的现金收费方式类:

    package com.designModel.chaper2_strategeModel.step2;
    
    /**
     * 现金收费类
     * @author samsung
     *
     */
    public abstract class CashFee {
        
        public abstract double acceptCash(double money); 
    }

    定义现金收费方式的实现类,分别是: 正常收费类, 折扣类, 返现类

    package com.designModel.chaper2_strategeModel.step2;
    
    //正常收费类
    public class NormalCashFee extends CashFee {
    
        @Override
        public double acceptCash(double money) {
            return money;
        }
    }
    package com.designModel.chaper2_strategeModel.step2;
    
    public class DiscountCashFee extends CashFee {
    
        private double discount = 0.00;
        
        public DiscountCashFee(double discount){
            this.discount = discount / 10;
        }
        
        @Override
        public double acceptCash(double money) {
            
            return this.discount * money;
        }
    
        public double getDiscount() {
            return discount;
        }
    
        public void setDiscount(double discount) {
            this.discount = discount;
        }
    
    }
    package com.designModel.chaper2_strategeModel.step2;
    
    public class ReturnCashFee extends CashFee {
    
        //基础金额
        private double baseCash;
        
        //返现金额
        private double returnCash;
        
        public ReturnCashFee(double baseCash, double returnCash){
            this.baseCash = baseCash;
            this.returnCash = returnCash;
        }
        
        @Override
        public double acceptCash(double money) {
            return money - Math.floor(money/300) * 50;
        }
        
    
        public double getBaseCash() {
            return baseCash;
        }
    
        public void setBaseCash(double baseCash) {
            this.baseCash = baseCash;
        }
    
        public double getReturnCash() {
            return returnCash;
        }
    
        public void setReturnCash(double returnCash) {
            this.returnCash = returnCash;
        }
        
    }

    在定义一个工厂类, 用来产生各种现金收费方式

    package com.designModel.chaper2_strategeModel.step2;
    
    import java.util.Scanner;
    
    
    public class CashFeeFactory {
        public static CashFee createCashFee(int type, double discount, double basePrice, double returnPrice){
            CashFee cashFee = null;
            switch(type){
                case 1:
                    cashFee = new NormalCashFee();
                    break;
                case 2:
                    cashFee = new DiscountCashFee(discount);
                    break;
                case 3:
                    cashFee = new ReturnCashFee(basePrice, returnPrice);
                    break;
            }
            
            return cashFee;
        }
        
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣类型(1 无折扣 2打折 3满减):");
            String zhekou = scan.nextLine();
            
            double discount = 0.0d;
            double basePrice = 0;
            double returnPrice = 0;
            if("2".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("输入折扣:");
                discount = Double.parseDouble(scan.nextLine());
            }
            
            if("3".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("基础金额:");
                basePrice = Double.parseDouble(scan.nextLine());
                scan = new Scanner(System.in);
                System.out.println("返现金额:");
                returnPrice = Double.parseDouble(scan.nextLine());
            }
            Double xiaoji = Double.parseDouble(price) * Integer.parseInt(num) * Double.parseDouble(zhekou)/10;
            CashFee cashFee = CashFeeFactory.createCashFee(1, discount, basePrice, returnPrice);
        }
    }

    完成了, 在main方法里, 我们模拟了客户端, 输入单价和数量,打印小票

     -------------------------------------------------------------------------------------------------

    思考: 

    使用简单工厂设计模式的优缺点:

    优点:

      1. 业务逻辑和前端展示相分离开了。业务逻辑的修改, 不影响前端代码

      2. 每一个业务逻辑单独一个类, 修改或者添加一个类, 不会影响到其他的类.

      3. 使用工厂类封装了业务逻辑类. 前端不需要知道到底每种业务逻辑具体怎么实现的. 只需要知道他的父类即可.

    缺点:

      1. 如果活动很频繁, 经常会搞各种各样的活动, 那么业务逻辑类就会有很多, 每一次都要增加一个类.

      2. 每增加一个类都要修改工厂类. 修改会非常频繁

     

    小结: 简单工厂设计模式虽然也能解决这个问题, 但这个模式只是解决对类的创建问题.

    --------------------------------------------------------------------------

    下面介绍策略设计模式:

      策略(Strategy)设计模式:定义了算法家族, 分别将算法封装起来, 让他们之间可以相互替换,此模式让算法的变化不会影响到使用算法的客户。

      封装变化点, 是面向对象的一种很重要的思维方式.

      下面以商场促销打印收银小票为例, 来分析策略设计模式

      

      1. 在策略设计模式中, 有各种算法: 所有的算法有一个算法父类: CashFee. 这个父类是一个抽象类. 里面有一个抽象方法acceptCash(double money)

      2. 所有的算法类实现抽象算法父类, 并实现抽象方法

      3. 重点: 有一个上下文对象. 这个对象封装了对算法类的调用

     下面看代码:

    1. 现金类算法, 以及现金的各种实现类算法

    package com.designModel.chaper2_strategeModel.step3;
    
    /**
     * 现金收费类
     * @author samsung
     *
     */
    public abstract class CashFee {
        
        public abstract double acceptCash(double money); 
    }
    package com.designModel.chaper2_strategeModel.step3;
    
    //正常收费类
    public class NormalCashFee extends CashFee {
    
        @Override
        public double acceptCash(double money) {
            return money;
        }
    }
    package com.designModel.chaper2_strategeModel.step3;
    
    public class DiscountCashFee extends CashFee {
    
        private double discount = 0.00;
        
        public DiscountCashFee(double discount){
            this.discount = discount / 10;
        }
        
        @Override
        public double acceptCash(double money) {
            
            return this.discount * money;
        }
    
        public double getDiscount() {
            return discount;
        }
    
        public void setDiscount(double discount) {
            this.discount = discount;
        }
    
    }
    package com.designModel.chaper2_strategeModel.step3;
    
    public class ReturnCashFee extends CashFee {
    
        //基础金额
        private double baseCash;
        
        //返现金额
        private double returnCash;
        
        public ReturnCashFee(double baseCash, double returnCash){
            this.baseCash = baseCash;
            this.returnCash = returnCash;
        }
        
        @Override
        public double acceptCash(double money) {
            return money - Math.floor(money/300) * 50;
        }
        
    
        public double getBaseCash() {
            return baseCash;
        }
    
        public void setBaseCash(double baseCash) {
            this.baseCash = baseCash;
        }
    
        public double getReturnCash() {
            return returnCash;
        }
    
        public void setReturnCash(double returnCash) {
            this.returnCash = returnCash;
        }
        
    }

    最后. 来看看上下文类

    package com.designModel.chaper2_strategeModel.step3;
    
    import java.util.Scanner;
    
    
    public class CashContext {
        private CashFee cashFee;
        public CashContext(CashFee cashFee)
        {
            this.cashFee = cashFee;
        }
        public static double getResult(double money){return cashFee.acceptCash(money);
        }
        
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣类型(1 无折扣 2打折 3满减):");
            String type = scan.nextLine();
            
            double discount = 0.0d;
            double basePrice = 0;
            double returnPrice = 0;
            
            if("2".equals(type)){
                scan = new Scanner(System.in);
                System.out.println("输入折扣:");
                discount = Double.parseDouble(scan.nextLine());
            }
            
            if("3".equals(type)){
                scan = new Scanner(System.in);
                System.out.println("基础金额:");
                basePrice = Double.parseDouble(scan.nextLine());
                scan = new Scanner(System.in);
                System.out.println("返现金额:");
                returnPrice = Double.parseDouble(scan.nextLine());
            }
            
            
            Double xianjin = Double.parseDouble(price) * Integer.parseInt(num);
            CashContext cc = null;
            switch(type){
                case "1":
                    cc = new CashContext(new NormalCashFee());
                    break;
                case "2":
                    cc = new CashContext(new DiscountCashFee(discount));
                    break;
                case "3":
                    cc = new CashContext(new ReturnCashFee(basePrice, returnPrice));
                    break;
            }
            
            cc.getResult(xianjin);
        }
    }

    分析:

    1. 业务逻辑和前端页面展示分开

    2. 有一个context上下文类, 在其内部引用了CashFee类. 构造方法定义了具体的实现类.

    3. 这样操作的问题: 客户端依然需要switch判断. 

    ----------------------------------------------

    策略设计模式和简单工厂设计模式相结合的方案:

    其他都不变: 变化的是CashContext类

    package com.designModel.chaper2_strategeModel.step4;
    
    import java.util.Scanner;
    
    
    public class CashContext {
        private CashFee cashFee;
        public CashContext(int type, double discount, double basePrice, double returnPrice)
        {
            switch(type){
                case 1:
                    cashFee = new NormalCashFee();
                    break;
                case 2:
                    cashFee = new DiscountCashFee(discount);
                    break;
                case 3:
                    cashFee = new ReturnCashFee(basePrice, returnPrice);
                    break;
          }
        }
        public static double getResult(double money){
            CashFee cashFee = null;
            return cashFee.acceptCash(money);
        }
        
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣类型(1 无折扣 2打折 3满减):");
            String zhekou = scan.nextLine();
            
            double discount = 0.0d;
            double basePrice = 0;
            double returnPrice = 0;
            
            if("2".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("输入折扣:");
                discount = Double.parseDouble(scan.nextLine());
            }
            
            if("3".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("基础金额:");
                basePrice = Double.parseDouble(scan.nextLine());
                scan = new Scanner(System.in);
                System.out.println("返现金额:");
                returnPrice = Double.parseDouble(scan.nextLine());
            }
            
            
            Double xianjin = Double.parseDouble(price) * Integer.parseInt(num);
            CashContext cc = new CashContext(1, discount, basePrice, returnPrice);
            cc.getResult(xianjin);
        }
    }

    我们将前端的switch转移到了CashContext的内部. 这样,前端只需要传递给我, 类型信息就可以了. 

    下面来看看: 简单工厂设计模式  和 策略模式+简单工厂设计模式的区别:

    对于客户端而言: 简单工厂设计模式, 客户端需要知道两个类, 而简单工厂+策略设计模式, 客户端只需要知道一个类, 减低了耦合性.

     ---------------------------------------------------------------------------------------

    使用策略模式的优点:

    1. 策略模式定义了一系列算法的方法, 从概念上来看, 这些算法完成的都是相同的工作, 只是实现不同,他可以以相同的方式调用算法, 减少了各种算法类与使用算法类之间的耦合

    2. 策略模式的另一个优点: 简化了单元测试, 因为每个算法都有自己的类, 可以通过自己的接口单独测试.

    3. 每个算法都是独立的, 修改其中一个, 不会影响其他的算法.

    4. 在策略模式中, 选择所用具体实现的职责有客户端对象承担, 并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力。 而策略模式和简单工厂设计模式结合后, 选择具体实现的职责也交给了Context来承担, 最大化的减轻了客户端的职责。这比期初的策略模式好用,但是依然不够完美。因为他在context里还是用到了switch,这样人很不爽。如何才能不用switch呢? 反射。 

     ---------------------------------------------------------------------------------------

     思考, 

    书上说: 为什么要用策略设计模式呢?

    因为商场打折的算法很多, 每次增加一个促销活动, 可能就会有一个新的算法. 这时候, 如果用简单工厂设计模式, 需要增加一个算法类, 同时修改工厂类. 这是, 最好使用策略设计模式.

    疑问: 使用策略设计模式. 每次增加一个算法, 也要增加一个算法类, 而且要修改CashContext类. 并没有得到简化呀

     

    应该是我修行不够, 还没能够理解, 忘懂得大神指点.

    -------------------------------------------------------------------

    后续---使用反射加载类, 这里的修改主要是对Context中构造方法的修改. 

    package com.designModel.chaper2_strategeModel.step5;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Scanner;
    
    
    public class CashContext {
        private CashFee cashFee = null;
        static Map<String, String> map = new HashMap<String, String>();
        static{
            map.put("1", "com.designModel.chaper2_strategeModel.step5.NormalCashFee");
            map.put("2", "com.designModel.chaper2_strategeModel.step5.DiscountCashFee");
            map.put("3", "com.designModel.chaper2_strategeModel.step5.ReturnCashFee");
        }
        
        public CashContext(String type, double discount, double basePrice, double returnPrice) throws ClassNotFoundException, NoSuchMethodException, SecurityException
        {
            /**
             * 使用反射加载策略算法
             */
            try {
                String className = map.get(type);
                System.out.println(className);
                Class cla = Class.forName(className);
                //Class cla = Class.forName("com.designModel.chaper2_strategeModel.step5.NormalCashFee");
                Constructor cons = cla.getConstructor(double.class,double.class,double.class);
                cashFee = (CashFee) cons.newInstance(discount, basePrice, returnPrice);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            /*switch(type){
                case 1:
                    cashFee = new NormalCashFee();
                    break;
                case 2:
                    cashFee = new DiscountCashFee(discount);
                    break;
                case 3:
                    cashFee = new ReturnCashFee(basePrice, returnPrice);
                    break;
            }*/
        }
        public double getResult(double money){
            return cashFee.acceptCash(money);
        }
        
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣类型(1:无折扣  2:打折  3:满减):");
            String zhekou = scan.nextLine();
            
            double discount = 0.0d;
            double basePrice = 0;
            double returnPrice = 0;
            
            if("2".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("输入折扣:");
                discount = Double.parseDouble(scan.nextLine());
            }
            
            if("3".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("基础金额:");
                basePrice = Double.parseDouble(scan.nextLine());
                scan = new Scanner(System.in);
                System.out.println("返现金额:");
                returnPrice = Double.parseDouble(scan.nextLine());
            }
            
            
            Double xianjin = Double.parseDouble(price) * Integer.parseInt(num);
            CashContext cc;
            try {
                cc = new CashContext(zhekou, discount, basePrice, returnPrice);
                double result = cc.getResult(xianjin);
                System.out.println("result:"+result);
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }
    }

    这样做的好处很明显, 不用每次增加一个类, 添加一个switch分支啦. 并且消除了switch分支. 

    今天有回顾了一下策略模式, 似乎有了一点点更加深入的理解.

    -----------------------------------------------------------------------

    下面来一个最终版的全部代码: 

    package com.designModel.chaper2_strategeModel.step5;
    
    /**
     * 现金收费类
     * @author samsung
     *
     */
    public abstract class CashFee {
        
        private double discount;
        private double basePrice;
        private double returnPrice;
        
        public CashFee(){}
        
        public CashFee(double discount, double basePrice, double returnPrice){
            this.discount = discount;
            this.basePrice = basePrice;
            this.returnPrice = returnPrice;
        }
        
        public abstract double acceptCash(double money);
    
    
        public double getDiscount() {
            return discount;
        }
    
    
        public void setDiscount(double discount) {
            this.discount = discount;
        }
    
    
        public double getBasePrice() {
            return basePrice;
        }
    
    
        public void setBasePrice(double basePrice) {
            this.basePrice = basePrice;
        }
    
    
        public double getReturnPrice() {
            return returnPrice;
        }
    
    
        public void setReturnPrice(double returnPrice) {
            this.returnPrice = returnPrice;
        }
    }
    package com.designModel.chaper2_strategeModel.step5;
    
    //正常收费类
    public class NormalCashFee extends CashFee {
    
        public NormalCashFee(double discount, double basePrice, double returnPrice){
            
        };
        
        public NormalCashFee(){}
        
        @Override
        public double acceptCash(double money) {
            return money;
        }
    }
    package com.designModel.chaper2_strategeModel.step5;
    
    public class ReturnCashFee extends CashFee {
    
        /**
         * @param baseCash 基础金额
         * @param returnCash 返现金额
         */
        
        public ReturnCashFee(double discount, double basePrice, double returnPrice){
            super(0, basePrice, returnPrice);
        }
        
        @Override
        public double acceptCash(double money) {
            return money - Math.floor(money/super.getBasePrice()) * super.getReturnPrice();
        }
        
    }
    package com.designModel.chaper2_strategeModel.step5;
    
    public class DiscountCashFee extends CashFee {
    
        
        public DiscountCashFee(){}
        
        public DiscountCashFee(double discount, double basePrice, double returnPrice){
            super(discount/10, 0, 0);
        }
        
        @Override
        public double acceptCash(double money) {
            return super.getDiscount() * money;
        }
    
    }
    package com.designModel.chaper2_strategeModel.step5;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Scanner;
    
    
    public class CashContext {
        private CashFee cashFee = null;
        static Map<String, String> map = new HashMap<String, String>();
        static{
            map.put("1", "com.designModel.chaper2_strategeModel.step5.NormalCashFee");
            map.put("2", "com.designModel.chaper2_strategeModel.step5.DiscountCashFee");
            map.put("3", "com.designModel.chaper2_strategeModel.step5.ReturnCashFee");
        }
        
        public CashContext(String type, double discount, double basePrice, double returnPrice) throws ClassNotFoundException, NoSuchMethodException, SecurityException
        {
            /**
             * 使用反射加载策略算法
             */
            try {
                String className = map.get(type);
                System.out.println(className);
                Class cla = Class.forName(className);
                //Class cla = Class.forName("com.designModel.chaper2_strategeModel.step5.NormalCashFee");
                Constructor cons = cla.getConstructor(double.class,double.class,double.class);
                cashFee = (CashFee) cons.newInstance(discount, basePrice, returnPrice);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
            /*switch(type){
                case 1:
                    cashFee = new NormalCashFee();
                    break;
                case 2:
                    cashFee = new DiscountCashFee(discount);
                    break;
                case 3:
                    cashFee = new ReturnCashFee(basePrice, returnPrice);
                    break;
            }*/
        }
        public double getResult(double money){
            return cashFee.acceptCash(money);
        }
        
        public static void main(String[] args) {
            Scanner scan = new Scanner(System.in);
            System.out.println("输入单价:");
            String price = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入数量:");
            String num = scan.nextLine();
            
            scan = new Scanner(System.in);
            System.out.println("输入折扣类型(1:无折扣  2:打折  3:满减):");
            String zhekou = scan.nextLine();
            
            double discount = 0.0d;
            double basePrice = 0;
            double returnPrice = 0;
            
            if("2".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("输入折扣:");
                discount = Double.parseDouble(scan.nextLine());
            }
            
            if("3".equals(zhekou)){
                scan = new Scanner(System.in);
                System.out.println("基础金额:");
                basePrice = Double.parseDouble(scan.nextLine());
                scan = new Scanner(System.in);
                System.out.println("返现金额:");
                returnPrice = Double.parseDouble(scan.nextLine());
            }
            
            
            Double xianjin = Double.parseDouble(price) * Integer.parseInt(num);
            CashContext cc;
            try {
                cc = new CashContext(zhekou, discount, basePrice, returnPrice);
                double result = cc.getResult(xianjin);
                System.out.println("result:"+result);
            } catch (Exception e) {
                e.printStackTrace();
            } 
        }
    }

    亲测代码可用, 一下是运行结果: 分为三种

    第一种: 正常收费

    第二种: 打折, 打5折

     

    第三种: 满减, 满300减50

     

    ------------------------------------------------------

    问题:

    使用反射, 依然会有问题, 从代码就可以看出. 为了能够使用反射调用通用的构造函数, 所以在CashFee中定义了一个带有三个参数的构造方法

    public CashFee(double discount, double basePrice, double returnPrice){
            this.discount = discount;
            this.basePrice = basePrice;
            this.returnPrice = returnPrice;
        }

    然后在3个子类中, 调用这个构造方法

    public NormalCashFee(double discount, double basePrice, double returnPrice){
            
    };
    public DiscountCashFee(double discount, double basePrice, double returnPrice){
        super(discount/10, 0, 0);
    }
    public ReturnCashFee(double discount, double basePrice, double returnPrice){
        super(0, basePrice, returnPrice);
    }

    可以明显看出, NormalCashFee类中的这个带三个参数的构造方法, 其内部根本没有任何操作. 

    而DiscountCashFee只想传递第一个参数.

    而ReturnCashFee只想传递第二个和第三个参数.

    可是为了能够使用反射. 他们必须传递本身所不必须的参数. 这一点感觉还是不好, 应该优化. 可是如何优化呢? 尚未可知. 有知道的朋友, 请给我留言.

     

     

     

     

     

     

     

     

  • 相关阅读:
    Jupyter notebook中的Cell and Line Magics
    numpy中array数组对象的储存方式(n,1)和(n,)的区别
    机器学习中的标准化方法(Normalization Methods)
    matplotlib添加子图(拼图功能)
    matplotlib.pyplot.plot详解
    一行代码让你的python运行速度提高100倍
    一个简单的Shell脚本(解决windows上文本在macos上乱码问题)
    解决Mac上打开txt文件乱码问题
    LaTeX中常用代码段snippets(持续更新)
    LaTeX实时预览中文
  • 原文地址:https://www.cnblogs.com/ITPower/p/9073386.html
Copyright © 2011-2022 走看看