精华内容
下载资源
问答
  • 装饰器模式(Decorator):结构型设计模式,为了实现类在不修改原始类的基础上进行动态的覆盖或者增加方法,该实现保持了跟原有类的层级关系。这种设计模式允许向一个现有的对象添加新的功能,同时又不改变其结构。...

    装饰器模式(Decorator):结构型设计模式,为了实现类在不修改原始类的基础上进行动态的覆盖或者增加方法,该实现保持了跟原有类的层级关系。这种设计模式允许向一个现有的对象添加新的功能,同时又不改变其结构。算是一种非常特殊的适配器模式。

    在实际业务中,有时候我们会创建了多层子类,但如果当子类层数超过三层,一般来说不太建议,这个时候可以考虑使用装饰器模式。

    Spring中的应用场景:在我们的项目中遇到这样一个问题:我们的项目需要连接多个数据库,而且不同的客户在每 次访问中根据需要会去访问不同的数据库。我们以往在 Spring 和 Hibernate 框架中总是配置一个数据 源,因而 SessionFactory 的 DataSource 属性总是指向这个数据源并且恒定不变,所有 DAO 在使用SessionFactory 的时候都是通过这个数据源访问数据库。但是现在,由于项目的需要,我们的 DAO 在 访问 SessionFactory 的时候都不得不在多个数据源中不断切换,问题就出现了:如何让SessionFactory 在执行数据持久化的时候,根据客户的需求能够动态切换不同的数据源?我们能不能 在 Spring 的框架下通过少量修改得到解决?是否有什么设计模式可以利用呢?
    首先想到在 Spring 的 ApplicationContext 中配置所有的 DataSource。这些 DataSource 可能是各 种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL 等,也可能是不同的数据源:比如Apache 提 供 的 org.apache.commons.dbcp.BasicDataSource 、 Spring 提 供 的org.springframework.jndi.JndiObjectFactoryBean 等。然后 SessionFactory 根据客户的每次 请求,将 DataSource 属性设置成不同的数据源,以到达切换数据源的目的。


    由于装饰器模式算是一种非常特殊的适配器模式,所以,我这边使用之前适配器模式中的那个例子进行改造,使用装饰器模式来实现。之前适配器模式的java例子

    依旧是新增登录类别,不过此时不同的是新增两个接口ISignInService,ISignInForThirdService.

    ISignInForThirdService是继承于ISignInService的,SignInForThirdService不再是对SignInService的继承,而是对ISignInForThirdService的实现

    类图

    具体实现:

    User类:

    package Decorator;
    
    public class User {
        private String username;
        private String password;
        private String mid;
        private String info;
    
        public User() {
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getMid() {
            return mid;
        }
    
        public void setMid(String mid) {
            this.mid = mid;
        }
    
        public String getInfo() {
            return info;
        }
    
        public void setInfo(String info) {
            this.info = info;
        }
    }
    

     ResultMsg:

    package Decorator;
    
    public class ResultMsg {
        private int code;
        private String msg;
        private Object data;
    
        public ResultMsg( int code, String msg, Object data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }
    

    ISignInService接口和SignInService类:

    package Decorator;
    
    public interface ISignInService {
        /**
         * 注册接口
         * @param username
         * @param password
         * @return
         */
        public ResultMsg register(String username, String password);
    
    
        /**
         * 登录的接口
         * @param username
         * @param password
         * @return
         */
        public ResultMsg login(String username, String password);
    }
    
    package Decorator;
    
    public class SignInService implements ISignInService {
        /**
         * 注册方法
         * @param username
         * @param password
         * @return
         */
        public ResultMsg register(String username, String password){
            return  new ResultMsg(200,"注册成功",new User());
        }
    
    
        /**
         * 登录的方法
         * @param username
         * @param password
         * @return
         */
        public ResultMsg login(String username, String password){
            System.out.println("登陆成功");
            return null;
        }
    }
    

     ISignInForThirdService接口和SignInForThirdService类:

    package Decorator;
    
    public interface ISignInForThirdService extends ISignInService {
        public ResultMsg loginForQQ(String openId);
    
        public ResultMsg loginForWechat(String openId);
    
        public ResultMsg loginForToken(String token);
    
        public ResultMsg loginForTelephone(String telephone, String code);
    
        public ResultMsg loginForRegister(String username, String password);
    
        public ResultMsg login(String username, String password);
    }
    

     

    package Decorator;
    
    public class SignInForThirdService implements ISignInForThirdService {
    
        private ISignInService service;
        public SignInForThirdService(ISignInService service){
            this.service = service;
        }
    
        @Override
        public ResultMsg register(String username, String password) {
            return service.register(username,password);
        }
    
        @Override
        public ResultMsg login(String username, String password) {
            return service.login(username,password);
        }
    
    
        public ResultMsg loginForQQ(String openId){
            //1、openId是全局唯一,我们可以把它当做是一个用户名(加长)
            //2、密码默认为QQ_EMPTY
            //3、注册(在原有系统里面创建一个用户)
    
            //4、调用原来的登录方法
            String QQDefaultPasswords = "QQ_EMPTY";
            //这里省略查重验证,默认为新用户,实际项目执行会有
    
            System.out.println("QQ登录");
    
            return loginForRegister(openId,QQDefaultPasswords);
        }
    
        public ResultMsg loginForWechat(String openId){
            String WechatDefaultPasswords = "WECHAT_EMPTY";
            System.out.println("wechat登录");
            return loginForRegister(openId,WechatDefaultPasswords);
    
        }
    
        public ResultMsg loginForToken(String token){
            //通过token拿到用户信息,然后再重新登陆了一次
            User user = new User();
            System.out.println("token自动登录");
            return login(user.getUsername(),user.getPassword());
        }
    
        public ResultMsg loginForTelephone(String telephone, String code){
    
            String telephoneDefaultPasswords = "TELEPHONE_EMPTY";
            System.out.println("手机号登录");
            return loginForRegister(telephone,telephoneDefaultPasswords);
        }
    
        public ResultMsg loginForRegister(String username, String password){
            this.register(username,password);
            return this.login(username,password);
        }
    
    }
    

    比较特殊的是实际的调用方式的实现SignInForThirdServiceTest:

    package Decorator;
    
    public class SignInForThirdServiceTest {
        public static void main(String[] args) {
    
            ISignInForThirdService iSignInForThirdService = new SignInForThirdService(new SignInService());
    
            //原来的功能依旧对外开放,依旧保留
            //新的功能同样的也可以使用
            iSignInForThirdService.loginForQQ("sdfgdgfwresdf9123sdf");
            iSignInForThirdService.loginForTelephone("1560017471","sdha");
            iSignInForThirdService.loginForToken("dsajdsakldjksafjhfkasljkla");
            iSignInForThirdService.loginForWechat("dhafkahkjdsada");
    
        }
    }
    

    使用SignInForThirdService来装饰SignInService;

    执行结果:

    以上就是一个装饰器模式的实现了。

    可以看到装饰器模式可以替代继承,来实现功能。因此,但我们要实现一个类的功能扩展的时候,可以考虑装饰器模式。

    那么装饰器模式和适配器模式的差别是哪些呢?

    装饰器模式适配器模式
    是一种非常特别的适配器模式 可以不保留层级关系

    装饰者和被装饰者都要实现同一个接口,

    主要目的是为了扩展,依旧保留OOP关系 

    适配者和被适配者没有必然的层级联系

    通常采用代理或者继承形式进行包装

     

    满足is-a的关系  满足has-a关系
    注重的是覆盖、扩展  注重兼容、转换

    在Spring 源码中,但类名里面有 Wrapper,Decorator,基本上可以认为是装饰器模式。

     

    展开全文
  • 戳蓝字"Web前端严选"关注我们哦!前言初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分享给...

    戳蓝字"

    Web前端严选

    "

    关注我们哦

    前言

    初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分享给大家,不喜勿喷。

    本项目是使用jsDecorator装饰器搭建,如果大家项目用的是ts,那么使用装饰器方法跟本文介绍的不同,请自行参考ts 使用装饰器。需要注意的是,使用这种模式,变量会存在污染,所以不能出现重名变量。

    什么是Decorator

    Decorator装饰器是一种类class相关的语法,所以Decorator装饰器只能用在class类里面,在普通的语法或表达式上不能使用。个人理解:装饰器可以给我们提供一层拦截的作用,先执行装饰器里面的东西,后执行我们的操作。具体请看阮一峰老师的《装饰器》。

    安装

    组件

    npm install --save vue-class-component
    npm install --save vue-property-decorator
    

    配置

    在项目的根目录babel.config.js进行配置如下

    module.exports = {
      presets: [
        '@vue/app'
      ],
      plugins: [
        ['@babel/plugin-proposal-decorators', { legacy: true }],
        ['@babel/plugin-proposal-class-properties', { loose: true }],
      ]
    }
    

    在项目的根目录jsconfig.json进行配置如下

    {
        "compilerOptions": {
          "experimentalDecorators": true
        }
    }
    

    使用方法

    我这里就介绍一下在Vue中常用的几个方法,具体详细的可以看这里Vue-property-decorator

    生命周期、methods、data

    这些写法都跟原来一样,直接写就行,看如下案例对比

    原写法

    <script>
    export default {
        data() {
            return {
                msg: "hello 蛙人"
            }
        },
        created() {
        
        },
        methods: {
            test() {
            
            }
        }
    }
    </script>
    

    装饰器写法

    <script>
    import { Vue } from 'vue-property-decorator'
    class App extends Vue {
        msg = "hello 蛙人"
        created() {
        
        }
        test() {
        
        }
    }
    export default App
    </script>
    

    Emit

    原写法

    <script>
    export default {
      methods: {
        send() {
          this.$emit("custom", 123)
        }
      }
    }
    </script>
    

    装饰器写法

    <script>
    import { Vue, Emit } from 'vue-property-decorator'
    class Hello extends Vue {
      created() {
        this.send()
      }
    
      @Emit("custom")
      send() {
        return 123
      }
    }
    export default Hello
    </script>
    

    Provide

    原写法

    <script>
    export default {
      provide() {
        return {
          msg: this.msg
        }
      }
    }
    </script>
    

    装饰器写法

    <script>
    class App extends Vue {
      @Provide() msg = this.msg
      msg = "hello 蛙人"
    }
    export default App
    </script>
    

    Inject

    原写法

    export default {
      inject: {
        msg: {
          default: () => "",
          required: true
        } 
      }
    }
    </script>
    

    装饰器写法

    import { Vue, Component,Inject } from 'vue-property-decorator'
    @Component
    class Hello extends Vue {
      @Inject({ required: true, default: () => "" }) msg
    }
    export default Hello
    

    Prop

    原写法

    <script>
    export default {
      props: {
        msg: {
          type: () => String,
          required: true
        }
      }
    }
    </script>
    

    装饰器写法

    <script>
    import { Vue, Prop } from 'vue-property-decorator'
    class Hello extends Vue {
      @Prop({ required: true, type: String }) msg
    }
    export default Hello
    </script>
    

    PropSync

    原写法

    // 父组件
    <HelloWorld :msg.sync="msg" v-show="msg"/>
    
    // 子组件
    <script>
    export default {
      props: {
        msg: {
          require: true
        }
      },
      created() {
        setTimeout(() => {
          this.test()
        }, 5000)
      },
      methods: {
        test() {
          this.$emit("update:msg", false)
        }
      }
    }
    </script>
    

    装饰器写法

    @PropSync第一个参数则是,this.$emit("update:msg")里面的msg, 语句后面则跟着变量

    <script>
    import { Vue, Component, PropSync } from 'vue-property-decorator'
    @Component
    class Hello extends Vue {
      @PropSync("msg", { required: true }) variable
      created() {
        setTimeout(() => {
          this.variable = false
        }, 5000)
      }
    }
    export default Hello
    </script>
    

    Watch

    原写法

    export default {
      data() {
        return {
          str: 123  
        }
      },
      created() {
        setTimeout(() => {
          this.str = 12
        }, 5000)
      },
      watch: {
        str: function(newVal, oldVal) {
          console.log(newVal, oldVal)
        }
      }
    }
    </script>
    

    装饰器写法

    import { Vue, Component, Watch } from 'vue-property-decorator'
    @Component
    class Hello extends Vue {
      str = 123
    
      created() {
        setTimeout(() => {
          this.str = 12
        }, 2000)
      }
    
      @Watch("str", {deep: true})
      test(newVal, oldVal) {
        console.log(newVal, oldVal)
      }
    }
    export default Hello
    

    Computed

    原写法

    <script>
    export default {
      computed: {
        test: {
           get() {
             return this.msg
           },
           set(val) {
             return this.msg = val
           }
         }
       }
    }
    </script>
    

    装饰器写法

    <script>
    import { Vue, Component } from 'vue-property-decorator'
    @Component
    class App extends Vue {
      get test() {
        return this.msg
      }
      set test(val) {
        return this.msg = val
      }
    
    }
    export default App
    

    Model

    有时候我们想给组件写一个v-model方法就可以这样,如下

    原写法

    // 父组件
    <HelloWorld :msg="msg" v-model="msg"/>
    
    // 子组件
    <input type="text" @input="test" :value="msg">
    
    <script>
    export default {
      props: {
        msg: {
          require: true
        }
      },
      model: {
        prop: "msg",
        event: "input"
      },
      methods: {
        test(e) {
          this.$emit("input", e.target.value)
        }
      }
    }
    </script>
    

    装饰器写法

    // 父组件
    <HelloWorld :msg="msg" v-model="msg"/>
    
    // 子组件
    <input type="text" @input="test" :value="msg">
    
    <script>
    import { Vue, Component, Model, Emit } from 'vue-property-decorator'
    @Component
    class Hello extends Vue {
      @Model("input", {default: () => ""}) msg
      
      test(e) {
        this.send(e)
      }
      @Emit("input")
      send(e) {
        return e.target.value
      }
    }
    export default Hello
    </script>
    

    Ref

    原写法

    <HelloWorld :msg="msg" ref="val"/>
    
    <script>
    export default {
      name: 'App',
      components: {
        HelloWorld
      },
      data() {
        return {
          msg: "hello 蛙人"
        }
      },
      mounted() {
        console.log(this.$refs.val)
      },
    }
    </script>
    

    装饰器写法

    <HelloWorld :msg="msg" ref="val"/>
    
    <script>
    import { Vue, Component, Ref } from 'vue-property-decorator'
    @Component({
      components: {
        HelloWorld
      }
    })
    class App extends Vue {
      @Ref("val") val
      msg = "hello 蛙人"
      
      mounted() {
        console.log(this.val)
      }
    }
    export default App
    </script>
    

    Component

    该方法是从组件库中导入,如果使用使用生命周期方法,记得引入此装饰器,否则不生效。该装饰器接收一个对象里面也可以写原生Vue方法。

    <script>
    import { Vue, Component } from 'vue-property-decorator'
    @Component({
        components: {
            
        },
        watch: {
            str: function(val) {
                console.log(val)
            }
        }
    })
    export class App extends Vue {}
    </script>
    

    扩展

    当然了可以根据自己的需求扩展封装Decorator装饰器,装饰器接收三个参数

    • 目标对象

    • 目标key

    • 描述对象

    这里不明白描述对象属性可以看这篇文章《深入理解JavaScript对象》

    <script>
    function Decorator(data) {
        return (vue, key, describe) => {
          // vue 当前执行环境对象
          // key 当前装饰器函数对象 test
          // describe 描述对象里面value是函数
          let fn = describe.value
          describe.value = function () {
            let status = window.confirm(data)
            if (status) return fn()
          }
        }
    }
    import { Vue, Component } from 'vue-property-decorator'
    @Component
    class App extends Vue {
      @Decorator("请点击确定")
      test() { 
        window.confirm("你是否完成了")
      }
    }
    export default App
    </script>
    

    上面example中,可扩展自己的Decorator装饰器,装饰器就相当于起一层拦截作用,先执行装饰器里面的操作,在执行我们函数本身的逻辑操作。我这里只做一个案例哈,具体看你们的需求。

    感谢

    ❤️ 感谢大家

    如果你觉得这篇内容对你挺有有帮助的话:

    1. 看到这里了就点个在看支持一下吧,你的[在看]是我创作的动力;

    2. 关注公众号【Web前端严选】,进交流群,定期为你推送好文。

    添加个人微信,进群与小伙伴一起玩耍(已经推出)~

    展开全文
  • 通常情况下,我们如果要给对象添加功能,要么...装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能,并且它的本质就是动态组合,一句话,动态是手段,组合才是目的。 也就是说,在这种模式...

    通常情况下,我们如果要给对象添加功能,要么直接修改对象添加相应的功能,要么派生对应的子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类这种方式并不可取。

    在面向对象的设计中,我们也应该尽量使用对象组合,而不是对象继承来扩展和复用功能。装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能,并且它的本质就是动态组合,一句话,动态是手段,组合才是目的。

    也就是说,在这种模式下,我们可以对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,理解了不???

    还可以理解为,我们不去修改已有的类,而是通过创建另外一个装饰器类,通过这个装饰器类去动态的扩展其需要修改的内容。而它的好处也是显而易见的,如下:

    • 1、我们可以保证类的层次不会因过多而发生混乱。
    • 2、当我们需求的修改很小时,不用改变原有的数据结构。

    我们来看下《PHP设计模式》里面的一个案例:

    /** * 被修饰类 现在的需求: 要求能够动态为CD添加音轨、能显示CD音轨列表。 显示时应采用单行并且为每个音轨都以音轨好为前缀。 */
    
    class CD {
        public $trackList;
    
        function __construct()   {
            # code...
            $this->trackList=array();
        }
    
        public function addTrack($track){
            $this->trackList[]=$track;
        }
    
        public function getTrackList(){
            $output=" ";
            foreach ($this->trackList as $key => $value) {
                # code...
                $output.=($key+1).") {$value}. ";
            }
            return $output;
        }
    }
    
    /* 现在需求发生变化: 要求将当前实例输出的音轨都采用大写形式。 这个需求并不是一个变化特别大的需求,不需要修改基类或创建一个父子关系的子类,此时创建一个基于装饰器模式的装饰器类。 */
    
    class CDTrackListDecoratorCaps{
        private $_cd;
    
        public function __construct(CD $CD){
            $this->_cd=$CD;
        }
    
        public function makeCaps(){
            foreach ($this->_cd->trackList as $key => $value) {
                # code...
                $this->_cd->trackList[$key]=strtoupper($value); //转换成大写
            }
        }
    }
    
    //客户端测试
    $myCD=new CD();
    $trackList=array(   "what It Means",   "brr",   "goodBye"  );
    foreach ($trackList as $key => $value) {
        # code...
        $myCD->addTrack($value);
    }
    
    $myCDCaps=new CDTrackListDecoratorCaps($myCD);
    $myCDCaps->makeCaps();
    print "The CD contains the following tracks:".$myCD->getTrackList();

    来看一个比较通俗但是比较简单的案例:

    • 设计一个UserInfo类,里面有UserInfo数组,用于存储用户名信息
    • 通过addUser来添加用户名
    • getUserList方法将打印出用户名信息
    • 现在需要将添加的用户信息变成大写的,我们需要不改变原先的类,并且不改变原先的数据结构
    • 我们设计了一个UserInfoDecorate类来完成这个需求的操作,就像装饰一样,给原先的数据进行了装修
    • 装饰器模式有些像适配器模式,但是一定要注意,装饰器主要是不改变现有对象数据结构的前提

    代码如下:

    UserInfo.php
    //装饰器模式,对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,可以使用装饰器设计模式
    class UserInfo {
     
    	public $userInfo = array(); 
    		
    	public function addUser($userInfo) {
    		$this->userInfo[] = $userInfo;
    	}
    		
    	public function getUserList() {
    		print_r($this->userInfo);
    	}
    }
    
    //UserInfoDecorate 装饰一样,改变用户信息输出为大写格式,不改变原先UserInfo类
    <?php
    include("UserInfo.php");
    class UserInfoDecorate {
    	
    	public function makeCaps($UserInfo) {
    		foreach ($UserInfo->userInfo as &$val) {
    			$val = strtoupper($val);
    		}
    	}
    	
    }
     
    $UserInfo = new UserInfo;
    $UserInfo->addUser('zhu');
    $UserInfo->addUser('initphp');
    $UserInfoDecorate = new UserInfoDecorate;
    $UserInfoDecorate->makeCaps($UserInfo);
    $UserInfo->getUserList();

    到此,咱们应该是对于装饰器模式有了一个大概的了解,接下来咱们看一下构建装饰器模式的案例,网上的,先来看目录结构:

    |decorator  #项目根目录
    |--Think  #核心类库
    |----Loder.php  #自动加载类
    |----decorator.php  #装饰器接口
    |----colorDecorator.php  #颜色装饰器
    |----sizeDecorator.php  #字体大小装饰器
    |----echoText.php  #被装饰者
    |--index.php #单一的入口文件
    

    完事就是来构建装饰器接口,Think/decorator.php,如下:

    <?php
    /**
     * 装饰器接口
     * Interface decorator
     * @package Think
     */
    namespace Think;
    
    interface decorator{
        public function beforeDraw();
        public function afterDraw();
    }

    再来就是颜色装饰器 Think/colorDecorator.php,如下:

    <?php
    /**
     * 颜色装饰器
     */
    namespace Think;
    
    class colorDecorator implements decorator{
        protected $color;
        public function __construct($color) {
            $this->color = $color;
        }
    
        public function beforeDraw() {
            echo "color decorator :{$this->color}\n";
        }
    
        public function afterDraw() {
            echo "end color decorator\n";
        }
    }
    

    还有就是字体大小装饰器 Think/sizeDecorator.php,如下:

    <?php
    /**
     * 字体大小装饰器
     */
    namespace Think;
    
    class sizeDecorator implements decorator{
        protected $size;
    
        public function __construct($size) {
            $this->size = $size;
        }
    
        public function beforeDraw() {
            echo "size decorator {$this->size}\n";
        }
    
        public function afterDraw() {
            echo "end size decorator\n";
        }
    }
    
    

    还有被装饰者 Think/echoText.php,如下:

    <?php
    /**
     * 被装饰者
     */
    namespace Think;
    
    class echoText {
        protected $decorator = array();  //存放装饰器
    
        //装饰方法
        public function index() {
            //调用装饰器前置操作
            $this->before();
            echo "你好,我是装饰器\n";
            //执行装饰器后置操作
            $this->after();
        }
    
        public function addDecorator(Decorator $decorator) {
            $this->decorator[] = $decorator;
        }
    
        //执行装饰器前置操作 先进先出
        public function before() {
            foreach ($this->decorator as $decorator){
                $decorator->beforeDraw();
            }
        }
    
        //执行装饰器后置操作 先进后出
        public function after() {
            $decorators = array_reverse($this->decorator);
            foreach ($decorators as $decorator){
                $decorator->afterDraw();
            }
        }
    }
    

    再来个自动加载 Think/Loder.php,如下:

    <?php
    namespace Think;
    
    class Loder{
        static function autoload($class){
            require BASEDIR . '/' .str_replace('\\','/',$class) . '.php';
        }
    }
    
    
    

    最后就是入口文件index.php了,如下:

    <?php
    define('BASEDIR',__DIR__);
    include BASEDIR . '/Think/Loder.php';
    spl_autoload_register('\\Think\\Loder::autoload');
    
    //实例化输出类
    $echo = new \Think\echoText();
    //增加装饰器
    $echo->addDecorator(new \Think\colorDecorator('red'));
    //增加装饰器
    $echo->addDecorator(new \Think\sizeDecorator('12'));
    //装饰方法
    $echo->index();
    

    咱最后再来一个案例啊,就是Web服务层 —— 为 REST 服务提供 JSON 和 XML 装饰器,来看代码:

    RendererInterface.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * RendererInterface接口
     */
    interface RendererInterface
    {
        /**
         * render data
         *
         * @return mixed
         */
        public function renderData();
    }
    
    Webservice.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * Webservice类
     */
    class Webservice implements RendererInterface
    {
        /**
         * @var mixed
         */
        protected $data;
    
        /**
         * @param mixed $data
         */
        public function __construct($data)
        {
            $this->data = $data;
        }
    
        /**
         * @return string
         */
        public function renderData()
        {
            return $this->data;
        }
    }
    
    Decorator.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * 装饰器必须实现 RendererInterface 接口, 这是装饰器模式的主要特点,
     * 否则的话就不是装饰器而只是个包裹类
     */
    
    /**
     * Decorator类
     */
    abstract class Decorator implements RendererInterface
    {
        /**
         * @var RendererInterface
         */
        protected $wrapped;
    
        /**
         * 必须类型声明装饰组件以便在子类中可以调用renderData()方法
         *
         * @param RendererInterface $wrappable
         */
        public function __construct(RendererInterface $wrappable)
        {
            $this->wrapped = $wrappable;
        }
    }
    
    RenderInXml.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * RenderInXml类
     */
    class RenderInXml extends Decorator
    {
        /**
         * render data as XML
         *
         * @return mixed|string
         */
        public function renderData()
        {
            $output = $this->wrapped->renderData();
    
            // do some fancy conversion to xml from array ...
    
            $doc = new \DOMDocument();
    
            foreach ($output as $key => $val) {
                $doc->appendChild($doc->createElement($key, $val));
            }
    
            return $doc->saveXML();
        }
    }
    
    RenderInJson.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * RenderInJson类
     */
    class RenderInJson extends Decorator
    {
        /**
         * render data as JSON
         *
         * @return mixed|string
         */
        public function renderData()
        {
            $output = $this->wrapped->renderData();
    
            return json_encode($output);
        }
    }
    
    Tests/DecoratorTest.php
    <?php
    
    namespace DesignPatterns\Structural\Decorator\Tests;
    
    use DesignPatterns\Structural\Decorator;
    
    /**
     * DecoratorTest 用于测试装饰器模式
     */
    class DecoratorTest extends \PHPUnit_Framework_TestCase
    {
    
        protected $service;
    
        protected function setUp()
        {
            $this->service = new Decorator\Webservice(array('foo' => 'bar'));
        }
    
        public function testJsonDecorator()
        {
            // Wrap service with a JSON decorator for renderers
            $service = new Decorator\RenderInJson($this->service);
            // Our Renderer will now output JSON instead of an array
            $this->assertEquals('{"foo":"bar"}', $service->renderData());
        }
    
        public function testXmlDecorator()
        {
            // Wrap service with a XML decorator for renderers
            $service = new Decorator\RenderInXml($this->service);
            // Our Renderer will now output XML instead of an array
            $xml = '<?xml version="1.0"?><foo>bar</foo>';
            $this->assertXmlStringEqualsXmlString($xml, $service->renderData());
        }
    
        /**
         * The first key-point of this pattern :
         */
        public function testDecoratorMustImplementsRenderer()
        {
            $className = 'DesignPatterns\Structural\Decorator\Decorator';
            $interfaceName = 'DesignPatterns\Structural\Decorator\RendererInterface';
            $this->assertTrue(is_subclass_of($className, $interfaceName));
        }
    
        /**
         * Second key-point of this pattern : the decorator is type-hinted
         *
         * @expectedException \PHPUnit_Framework_Error
         */
        public function testDecoratorTypeHinted()
        {
            if (version_compare(PHP_VERSION, '7', '>=')) {
                throw new \PHPUnit_Framework_Error('Skip test for PHP 7', 0, __FILE__, __LINE__);
            }
    
            $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
        }
    
        /**
         * Second key-point of this pattern : the decorator is type-hinted
         *
         * @requires PHP 7
         * @expectedException TypeError
         */
        public function testDecoratorTypeHintedForPhp7()
        {
            $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
        }
    
        /**
         * The decorator implements and wraps the same interface
         */
        public function testDecoratorOnlyAcceptRenderer()
        {
            $mock = $this->getMock('DesignPatterns\Structural\Decorator\RendererInterface');
            $dec = $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array($mock));
            $this->assertNotNull($dec);
        }
    }

    好啦,本次记录就到这里了。

    如果感觉不错的话,请多多点赞支持哦。。。

    展开全文
  • 模拟单点登录类 itstack-demo-design-9-01 使用一坨代码实现业务需求 itstack-demo-design-9-02 通过设计模式优化改造代码,产生对比性从而学习 三、装饰器模式介绍 初看上图感觉装饰器模式有点像俄罗斯套娃、某众...

    作者:小傅哥
    博客:https://bugstack.cn

    沉淀、分享、成长,让自己和他人都能有所收获!😄

    一、前言

    对于代码你有编程感觉吗

    很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固定的流程编写出流水式的代码外,很难去思考整套功能服务的扩展性和可维护性。尤其是在一些较大型的功能搭建上,比较缺失一些驾驭能力,从而导致最终的代码相对来说不能做到尽善尽美。

    江洋大盗与江洋大偷

    两个本想描述一样的意思的词,只因一字只差就让人觉得一个是好牛,一个好搞笑。往往我们去开发编程写代码时也经常将一些不恰当的用法用于业务需求实现中,当却不能意识到。一方面是由于编码不多缺少较大型项目的实践,另一方面是不思进取的总在以完成需求为目标缺少精益求精的工匠精神。

    书从来不是看的而是用的

    在这个学习资料几乎爆炸的时代,甚至你可以轻易就获取几个T的视频,小手轻轻一点就收藏一堆文章,但却很少去看。学习的过程从不只是简单的看一遍就可以,对于一些实操性的技术书籍,如果真的希望学习到知识,那么一定是把这本书用起来而绝对不是看起来。

    二、开发环境

    1. JDK 1.8
    2. Idea + Maven
    3. 涉及工程三个,可以通过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
    工程描述
    itstack-demo-design-9-00场景模拟工程;模拟单点登录类
    itstack-demo-design-9-01使用一坨代码实现业务需求
    itstack-demo-design-9-02通过设计模式优化改造代码,产生对比性从而学习

    三、装饰器模式介绍

    装饰器模式,图片来自 refactoringguru.cn

    初看上图感觉装饰器模式有点像俄罗斯套娃、某众汽车🚕,而装饰器的核心就是再不改原有类的基础上给类新增功能。不改变原有类,可能有的小伙伴会想到继承、AOP切面,当然这些方式都可以实现,但是使用装饰器模式会是另外一种思路更为灵活,可以避免继承导致的子类过多,也可以避免AOP带来的复杂性。

    你熟悉的场景很多用到装饰器模式

    new BufferedReader(new FileReader(""));,这段代码你是否熟悉,相信学习java开发到字节流、字符流、文件流的内容时都见到了这样的代码,一层嵌套一层,一层嵌套一层,字节流转字符流等等,而这样方式的使用就是装饰器模式的一种体现。

    四、案例场景模拟

    场景模拟;单点登录功能扩展

    在本案例中我们模拟一个单点登录功能扩充的场景

    一般在业务开发的初期,往往内部的ERP使用只需要判断账户验证即可,验证通过后即可访问ERP的所有资源。但随着业务的不断发展,团队里开始出现专门的运营人员、营销人员、数据人员,每个人员对于ERP的使用需求不同,有些需要创建活动,有些只是查看数据。同时为了保证数据的安全性,不会让每个用户都有最高的权限。

    那么以往使用的SSO是一个组件化通用的服务,不能在里面添加需要的用户访问验证功能。这个时候我们就可以使用装饰器模式,扩充原有的单点登录服务。但同时也保证原有功能不受破坏,可以继续使用。

    1. 场景模拟工程

    itstack-demo-design-9-00
    └── src
        └── main
            └── java
                └── org.itstack.demo.design
                    ├── HandlerInterceptor.java
                    └── SsoInterceptor.java
    
    • 这里模拟的是spring中的类:HandlerInterceptor,实现起接口功能SsoInterceptor模拟的单点登录拦截服务。
    • 为了避免引入太多spring的内容影响对设计模式的阅读,这里使用了同名的类和方法,尽可能减少外部的依赖。

    2. 场景简述

    2.1 模拟Spring的HandlerInterceptor

    public interface HandlerInterceptor {
    
        boolean preHandle(String request, String response, Object handler);
    
    }
    
    • 实际的单点登录开发会基于;org.springframework.web.servlet.HandlerInterceptor 实现。

    2.2 模拟单点登录功能

    public class SsoInterceptor implements HandlerInterceptor{
    
        public boolean preHandle(String request, String response, Object handler) {
            // 模拟获取cookie
            String ticket = request.substring(1, 8);
            // 模拟校验
            return ticket.equals("success");
        }
    
    }
    
    • 这里的模拟实现非常简单只是截取字符串,实际使用需要从HttpServletRequest request对象中获取cookie信息,解析ticket值做校验。
    • 在返回的里面也非常简单,只要获取到了success就认为是允许登录。

    五、用一坨坨代码实现

    此场景大多数实现的方式都会采用继承类

    继承类的实现方式也是一个比较通用的方式,通过继承后重写方法,并发将自己的逻辑覆盖进去。如果是一些简单的场景且不需要不断维护和扩展的,此类实现并不会有什么,也不会导致子类过多。

    1. 工程结构

    itstack-demo-design-9-01
    └── src
        └── main
            └── java
                └── org.itstack.demo.design
                    └── LoginSsoDecorator.java
    
    • 以上工程结构非常简单,只是通过 LoginSsoDecorator 继承 SsoInterceptor,重写方法功能。

    2. 代码实现

    public class LoginSsoDecorator extends SsoInterceptor {
    
        private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();
    
        static {
            authMap.put("huahua", "queryUserInfo");
            authMap.put("doudou", "queryUserInfo");
        }
    
        @Override
        public boolean preHandle(String request, String response, Object handler) {
            // 模拟获取cookie
            String ticket = request.substring(1, 8);
            // 模拟校验
            boolean success = ticket.equals("success");
    
            if (!success) return false;
    
            String userId = request.substring(9);
            String method = authMap.get(userId);
    
            // 模拟方法校验
            return "queryUserInfo".equals(method);
        }
    
    }
    
    • 以上这部分通过继承重写方法,将个人可访问哪些方法的功能添加到方法中。
    • 以上看着代码还算比较清晰,但如果是比较复杂的业务流程代码,就会很混乱。

    3. 测试验证

    3.1 编写测试类

    @Test
    public void test_LoginSsoDecorator() {
        LoginSsoDecorator ssoDecorator = new LoginSsoDecorator();
        String request = "1successhuahua";
        boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
        System.out.println("登录校验:" + request + (success ? " 放行" : " 拦截"));
    }
    
    • 这里模拟的相当于登录过程中的校验操作,判断用户是否可登录以及是否可访问方法。

    3.2 测试结果

    登录校验:1successhuahua 拦截
    
    Process finished with exit code 0
    
    • 从测试结果来看满足我们的预期,已经做了拦截。如果你在学习的过程中,可以尝试模拟单点登录并继承扩展功能。

    六、装饰器模式重构代码

    接下来使用装饰器模式来进行代码优化,也算是一次很小的重构。

    装饰器主要解决的是直接继承下因功能的不断横向扩展导致子类膨胀的问题,而是用装饰器模式后就会比直接继承显得更加灵活同时这样也就不再需要考虑子类的维护。

    在装饰器模式中有四个比较重要点抽象出来的点;

    1. 抽象构件角色(Component) - 定义抽象接口
    2. 具体构件角色(ConcreteComponent) - 实现抽象接口,可以是一组
    3. 装饰角色(Decorator) - 定义抽象类并继承接口中的方法,保证一致性
    4. 具体装饰角色(ConcreteDecorator) - 扩展装饰具体的实现逻辑

    通过以上这四项来实现装饰器模式,主要核心内容会体现在抽象类的定义和实现上。

    1. 工程结构

    itstack-demo-design-9-02
    └── src
        └── main
            └── java
                └── org.itstack.demo.design
                    ├── LoginSsoDecorator.java
                    └── SsoDecorator.java
    

    装饰器模式模型结构

    装饰器模式模型结构

    • 以上是一个装饰器实现的类图结构,重点的类是SsoDecorator,这个类是一个抽象类主要完成了对接口HandlerInterceptor继承。
    • 当装饰角色继承接口后会提供构造函数,入参就是继承的接口实现类即可,这样就可以很方便的扩展出不同功能组件。

    2. 代码实现

    2.1 抽象类装饰角色

    public abstract class SsoDecorator implements HandlerInterceptor {
    
        private HandlerInterceptor handlerInterceptor;
    
        private SsoDecorator(){}
    
        public SsoDecorator(HandlerInterceptor handlerInterceptor) {
            this.handlerInterceptor = handlerInterceptor;
        }
    
        public boolean preHandle(String request, String response, Object handler) {
            return handlerInterceptor.preHandle(request, response, handler);
        }
    
    }
    
    • 在装饰类中有两个重点的地方是;1)继承了处理接口、2)提供了构造函数、3)覆盖了方法preHandle
    • 以上三个点是装饰器模式的核心处理部分,这样可以踢掉对子类继承的方式实现逻辑功能扩展。

    2.2 装饰角色逻辑实现

    public class LoginSsoDecorator extends SsoDecorator {
    
        private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);
    
        private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();
    
        static {
            authMap.put("huahua", "queryUserInfo");
            authMap.put("doudou", "queryUserInfo");
        }
    
        public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
            super(handlerInterceptor);
        }
    
        @Override
        public boolean preHandle(String request, String response, Object handler) {
            boolean success = super.preHandle(request, response, handler);
            if (!success) return false;
            String userId = request.substring(8);
            String method = authMap.get(userId);
            logger.info("模拟单点登录方法访问拦截校验:{} {}", userId, method);
            // 模拟方法校验
            return "queryUserInfo".equals(method);
        }
    }
    
    • 在具体的装饰类实现中,继承了装饰类SsoDecorator,那么现在就可以扩展方法;preHandle
    • preHandle的实现中可以看到,这里只关心扩展部分的功能,同时不会影响原有类的核心服务,也不会因为使用继承方式而导致的多余子类,增加了整体的灵活性。

    3. 测试验证

    3.1 编写测试类

    @Test
    public void test_LoginSsoDecorator() {
        LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
        String request = "1successhuahua";
        boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
        System.out.println("登录校验:" + request + (success ? " 放行" : " 拦截"));
    }
    
    • 这里测试了对装饰器模式的使用,通过透传原有单点登录类new SsoInterceptor(),传递给装饰器,让装饰器可以执行扩充的功能。
    • 同时对于传递者和装饰器都可以是多组的,在一些实际的业务开发中,往往也是由于太多类型的子类实现而导致不易于维护,从而使用装饰器模式替代。

    3.2 测试结果

    23:50:50.796 [main] INFO  o.i.demo.design.LoginSsoDecorator - 模拟单点登录方法访问拦截校验:huahua queryUserInfo
    登录校验:1successhuahua 放行
    
    Process finished with exit code 0
    
    • 结果符合预期,扩展了对方法拦截的校验性。
    • 如果你在学习的过程中有用到过单点登陆,那么可以适当在里面进行扩展装饰器模式进行学习使用。
    • 另外,还有一种场景也可以使用装饰器。例如;你之前使用某个实现某个接口接收单个消息,但由于外部的升级变为发送list集合消息,但你又不希望所有的代码类都去修改这部分逻辑。那么可以使用装饰器模式进行适配list集合,给使用者依然是for循环后的单个消息。

    七、总结

    • 使用装饰器模式满足单一职责原则,你可以在自己的装饰类中完成功能逻辑的扩展,而不影响主类,同时可以按需在运行时添加和删除这部分逻辑。另外装饰器模式与继承父类重写方法,在某些时候需要按需选择,并不一定某一个就是最好。
    • 装饰器实现的重点是对抽象类继承接口方式的使用,同时设定被继承的接口可以通过构造函数传递其实现类,由此增加扩展性并重写方法里可以实现此部分父类实现的功能。
    • 就像夏天热你穿短裤,冬天冷你穿棉裤,雨天挨浇你穿雨衣一样,你的根本本身没有被改变,而你的需求却被不同的装饰而实现。生活中往往比比皆是设计,当你可以融合这部分活灵活现的例子到代码实现中,往往会创造出更加优雅的实现方式。

    八、推荐阅读

    展开全文
  • 最近为公司一个项目写一个简单的网关,其实也就是一个filter做一些token解析/校验。但是光这一个filter就不简单,现在就说说里面运用的设计模式。 Filter的核心-责任链模式 Filter: 属于javaweb中一个重要的组件,...
  • 在许多初创公司和其他小型公司中往往需要从头开始一个项目。在开发过程中有太多的编程语言,体系结构等问题需要抉择,而这些问题往往需要设计模式来解决。...在Web开发中,我认为有四种最重要且常见的设计模式
  • js/ts 装饰器

    千次阅读 2018-08-28 14:47:58
    第一次听说装饰器是在多年前玩python的时候,在python的众多web框架中都大量使用了装饰器来抽象功能逻辑或注入元数据...下面我们简要了解一下装饰器模式,装饰器是什么,js/ts有什么类型的装饰器,使用装饰器的好处...
  • python开发本地WEB项目

    千次阅读 2018-10-19 11:15:03
    0. 基础 python版本 python-3.6.4 编辑 pycharm-2018.2.4 ...虚拟环境是系统的一个位置,在开发web项目时,需要安装的所有python库都必须安装在该环境中。 创建( 通过CMD终端窗口实现 ) 1) ...
  • 文章目录如何用Node和TypeScript配置RouterRouter简介通用的解决办法一对一映射自然路由映射TypeScript的解决办法使用自然路由映射使用1对1映射方式通过配置方式通过装饰器进行构建路由表装饰器使用例子装饰器引入...
  • 深入浅出理解Python装饰器

    千次阅读 2018-02-05 15:57:19
    今天我想和大家一起探讨的话题是Python中的装饰器。因为工作关系最近这段时间在频繁地使用...无论是在Python标准库还是第三方库中,我们越来越频繁地看到装饰器的身影,从某种程度上而言,Python中的装饰器是Python
  • MVC编辑模式 Django安装 创建Django项目 配置数据库 创建业务 激活应用 定义模型 在数据库中生成数据表 测试数据操作 启动服务器 Admin站点管理 修改数据表 自定义管理页面 关联对象 Python下有许多款...
  • 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人...
  • 装饰模式、代理模式

    千次阅读 2017-07-24 17:48:14
    1、装饰模式(Decorator): 2、代理模式(Proxy): 3、装饰模式和代理模式的区别: 4、装饰模式和策略模式的区别:
  • 从经典的MVC模式Web三层结构

    千次阅读 2015-09-01 17:13:27
    视图需要使用控制来处理用户的输入,如果要实现对用户输入的不同处理,可以更换控制,他们之间实现了策略模式。视图和控制之间关系的一个比较经典的解释,可以参考文献[2]P4 视图是模型的状态的显示,视图...
  • XX商城 XX商城........................................................................1. 项目需求分析...................................................... 6 1.1. 概念...................................
  • 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权...小白为大家收集了11个web前端开发,大企业实战项目案例+5W行源码!拿走玩去吧! 老规矩:转发+关注并私信小编:“资料”全部打包带走! 下面给大家简单介...
  • web应用中常用的设计模式

    千次阅读 2008-04-24 10:54:00
    推荐几个做应用常用的设计模式:策略模式、代理模式、装饰器模式、状态模式、工厂模式、单态模式、Template模板模式。为什么推荐这几个呢,因为对于我来讲,这几个是最经常用到的。下面我来讲讲这几个模式在我的开发...
  • 笔者在公司用 web 技术开发移动端应用已经有一年多的时间了,开始主要以 vue 技术栈配合 native 为主,目前演进成 vue + react native 技术架构,vue 主要负责开发 OA 业务,比如报销、出差、crm 等等,react native...
  • 装饰器广泛用于权限校验和缓存等场景,是学习Python Web开发的必备知识。Django项目中使用装饰器可以让代码将变得更干净、更可读、更可维护。今天小编我就带你看下Django自带的常用装饰器的应用场景及正确使用方法。...
  • 手把手教你用 tornado 设计 web 项目

    千次阅读 2019-12-10 18:25:11
    为简化前端设计,我们采用九宫格抽奖模式。 3.1 存储设计 3.1.1 会员表 CREATE TABLE member ( id INTEGER PRIMARY KEY AUTOINCREMENT , /*会员id,自动增量*/ account TEXT , /*账号,文本...
  • JAVA_WEB框架设计模式总结集(未完结)

    千次阅读 2016-08-28 13:53:44
    设计模式思想:设计模式是为了:增加代码的复用性(一次编码/多处使用),降低代码块之间的耦合度,提高代码块功能的专一性。 IT职场的小菜经常有这样的疑问:  为什么一个相似的功能,大牛一会儿就搞定,然后悠闲地...
  • web前端@css选择

    千次阅读 2019-09-03 14:13:52
    web前端@css选择超级详细 文章很长 需耐心阅读 内容总览 内容总览 学会使用CSS选择 熟记CSS样式和外观属性 熟练掌握CSS各种选择 熟练掌握CSS各种选择 熟练掌握CSS三种显示模式 熟练掌握CSS背景属性 熟练...
  • 认识web url详解: URL是Uniform Resource Locator的简写,统一资源定位符。 一个URL由以下几部分组成: scheme://host:port/path/?query-string=xxx#anchor 1 scheme:代表的是访问的协议,一般为http...
  • java Web、网络、设计模式综合篇(附答案)

    千次阅读 多人点赞 2021-05-29 21:18:26
    Java Web 一、jsp 和 servlet 有什么区别? 1、servlet是服务器端的Java程序,它担当客户端和服务端的中间层。 2、jsp全名为Java server pages,中文名叫Java服务器页面,其本质是一个简化的servlet设计。JSP是一...
  • 前言:花了一个上午整理出来的设计模式+自己的一些理解,希望帮助你们能更好的结合实际场景理解设计模式,本文不讨论代码,关于设计模式的代码,网上有很多,本文只讨论理解~~ 1:工厂方法模式:类的创建依赖工厂类...
  •  模板方法模式,这是一个在许多优秀的开源项目中LZ见的最多的一个设计模式,也是LZ觉得最为优秀的一个设计模式,所以这一章LZ会尽自己所能的去尽量将这个设计模式解释清楚。  模板方法模式,一般是为了统一子类的...
  • django WEB聊天室项目

    千次阅读 2019-03-13 15:06:21
    bbs系统项目中我们用到的ajax不多,但是在聊天室里用到的全是ajax,所以本项目的主要内容就是: 前端使用ajax传输json格式的数据是本节的重点以及 前端函数的的使用. http协议的特点是:短链接 ,服务器无法主动向客户端...
  • MVC与web三层架构

    2020-06-28 10:02:23
    有很多人对于MVC的设计模式web三层框架混为一谈,其实二者没什么关系。(三层框架:表述层(WEB层)、业务逻辑层(Business Logic),以及数据访问层(Data Access))。 一:1.什么是模式 设计模式(Design ...
  • 经典设计模式实战演练

    千次阅读 2018-07-03 02:45:11
    为了实现以上目的,前辈们从实践中总结出了一套可套用的武功招式,这就是设计模式。使用设计模式可以让你写出一手令人赏心悦目的代码。 我认为每一个后端开发者都应该学习设计模式,它是代码的精华,是程序发展的...
  • Web开发人员可能是目前世界上最受破坏和薪水不足的职业之一。许多人没有意识到他们对商业网站的运营以及看起来适当和高效所具有的实际价值。从命名网站到外观,这是一个至关重要的过程,不应将其视为理所当然。如果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,439
精华内容 8,175
关键字:

web项目装饰器模式