设计模式

2023-05-16

1.什么是模式

设计模式是对某些典型易变问题的特定解决方案,这些问题和解决方案经过分类总结,并且为了方便交流给每个解决方案都起了特定的名字。

模式是为了解决变化的问题,将变化的问题进行封装,让变化单独变化而互不影响,以达到项目系统的扩展性,模式是一种更高层次的代码复用。

设想一下,假如我们的项目是一次性项目,一旦发布,以后再也不会修改,那么我们完全没有必要使用设计模式,反而使用了设计模式会增加系统的复杂度,但是一次性的项目在我们实际工作环境中是极少的,如果实际工作环境中都是一次项目,那个时候我们开发人员都应该转行了。

2.为什么要使用模式

  • 每一个设计模式都是为了解决一类特定的问题而存在,是一种更高层级的复用

  • 每一个设计模式都有一个特定的名字,团队协作中,既可以减少沟通成本,同时也可以保证项目质量

  • 模式有助于开发人员提高解决问题的思考层次,能够让我们的系统更容易扩展

3.六大原则

前辈们经过长期经验积累总结了许多设计模式,如单例模式、工厂模式、享元模式、策略模式、观察者模式等等,这些模式都是为了解决某一类特定的问题而设计,但是我们在日常编码过程中可以把这些模式作为一种参考即可,我们在编码过程中满足模式的原则即认为是好的代码。

模式有以下六大原则

名称解释
单一职责要存在多于一个导致类变更的原因,通俗的说,即一个类只负责一项职责
里式代换子类可以扩展父类的功能,但不能改变父类原有的功能
依赖倒置高层模块不应该依赖低层模块,二者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象
接口隔离客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上
迪米特法则一个对象应该对其他对象保持最少的了解
开闭原则一个软件实体如类、模块和函数应该对扩展开放,对修改关闭

3.1 单一职责

单一职责看似简单,其实并不简单,这个原则告诉了我们划分一个类功能的标准,下面我们看一段常见的代码片段

<?php
/**
 * Class Login
 * @datetime 2020/7/12 4:13 PM
 * @author roach
 * @email jhq0113@163.com
 */
abstract class Login
{
    /**微信appId
     * @var string
     * @datetime 2020/7/12 4:11 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $wxAppid;

    /**微信密钥
     * @var string
     * @datetime 2020/7/12 4:11 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $wxAppSecret;

    /**微信登录
     * @return mixed
     * @datetime 2020/7/12 4:12 PM
     * @author roach
     * @email jhq0113@163.com
     */
    abstract public function wxLogin();

    /**手机号验证码登录
     * @return mixed
     * @datetime 2020/7/12 4:12 PM
     * @author roach
     * @email jhq0113@163.com
     */
    abstract public function phoneLogin();
}

以上例程代码是一个用户登录的代码片段,为了演示方便作者把wxLoginphoneLogin两个方法实现细节进行了屏蔽。

以上代码有什么问题吗?不都是这么做登录的吗?

从模式原则来分析,假如微信登录实现细节发生修改,我们要修改Login类,手机号验证码登录实现细节发生修改,我们同样要修改Login类,如果我们要增加一种QQ授权登录方式,同样我们要修改Login

在模式的理论中,只要发生修改就会有风险,那么我们怎么解决这个问题呢?按照单一职责原则,作者把代码做了如下拆分优化

<?php
/**
 * Class Login
 * @datetime 2020/7/12 4:13 PM
 * @author roach
 * @email jhq0113@163.com
 */
abstract class WxLogin
{
    /**微信appId
     * @var string
     * @datetime 2020/7/12 4:11 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public $wxAppid;

    /**微信密钥
     * @var string
     * @datetime 2020/7/12 4:11 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public $wxAppSecret;

    /**
     * @param array $params
     * @return mixed
     * @datetime 2020/7/12 4:20 PM
     * @author roach
     * @email jhq0113@163.com
     */
    abstract public function login($params = []);
}

/**
 * Class PhoneLogin
 * @datetime 2020/7/12 4:20 PM
 * @author roach
 * @email jhq0113@163.com
 */
abstract class PhoneLogin
{
    /**
     * @param array $params
     * @return mixed
     * @datetime 2020/7/12 4:20 PM
     * @author roach
     * @email jhq0113@163.com
     */
    abstract public function login($params = []);
}

作者将Login类一分为二,微信登录的appId等依赖也放到各自类中,这样调用端如果想实现登录功能选择调用类就可以了。

现在我们分析,假如微信登录实现要发生修改,我们直接修改WxLogin类就可以了,同样的,假如手机号验证码登录实现要发生修改,我们只需要修改PhoneLogin类就可以了;假如我们要增加一种QQ授权登录的方式,此时我们增加一个QqLogin类就可以了。

3.2 里式代换

里式代换原则强调,我们能够扩展基类的原有功能,但不能改变基类原有的功能,这样很容易出现问题,下面还是看一段示例代码

<?php
/**
 * Class Model
 * @datetime 2020/7/12 4:30 PM
 * @author roach
 * @email jhq0113@163.com
 */
class Model 
{
    /**
     * @var \PDO
     * @datetime 2020/7/12 4:29 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    private $_db;
    
    /**
    * @param \PDO $db
    * @datetime 2020/7/12 4:45 PM
    * @author roach
    * @email jhq0113@163.com
     */
    public function setDb(\PDO $db)
    {
        $this->_db = $db;
    }

    /**
     * @return \PDO
     * @datetime 2020/7/12 4:32 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function getDb()
    {
        if(is_null($this->_db)) {
            $this->setDb(new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach'));
        }
        return $this->_db;
    }

    /**
     * @param null $name
     * @return string
     * @datetime 2020/7/12 4:36 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function lastInsertId($name = null)
    {
        return $this->_db->lastInsertId($name);
    }
}

/**
 * Class UserModel
 * @datetime 2020/7/12 4:35 PM
 * @author roach
 * @email jhq0113@163.com
 */
class UserModel extends Model
{
    /**
     * @return PDO|string
     * @datetime 2020/7/12 4:35 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function getDb()
    {
        return MyModel::db();
    }
}

/**
 * Class MyModel
 * @datetime 2020/7/12 4:34 PM
 * @author roach
 * @email jhq0113@163.com
 */
class MyModel
{
    /**
     * @var \PDO
     * @datetime 2020/7/12 4:33 PM
     * @author roach
     * @email jhq0113@163.com
     */
    private static $_db;

    /**
     * @return PDO
     * @datetime 2020/7/12 4:34 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public static function db()
    {
        if(is_null(self::$_db)) {
            self::$_db = new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach');
        }
        return self::$_db;
    }
}

以上代码也是真实遇到的,分析以上代码,在多人并行开发的团队,一个开发发现Model类的db并非真正的单例,于是这个开发在不改变业务调用代码的基础上,增加了MyModel类实现了约定单例,但是在实现UserModelgetDb方法的时候,改变了基类Model原有的getDb方法给_db私有属性赋值,这样导致基类lastInsertId方法不能使用,此时如果我们使用基类可以正常调用lastInsertId方法,但是使用子类UserModel调用lastInsertId就会发生异常。

以上代码违背了里式代换原则,那么我们应该如何修改呢?作者对UserModel进行了如下优化

<?php
/**
 * Class UserModel
 * @datetime 2020/7/12 4:35 PM
 * @author roach
 * @email jhq0113@163.com
 */
class UserModel extends Model
{
    /**
     * @return \PDO
     * @datetime 2020/7/12 4:35 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function getDb()
    {
        $this->setDb(MyModel::db());
        return parent::getDb();
    }
}

这样修改后保证了单例,和里式代换

3.3 依赖倒置

依赖倒置的思想是个很经典扩展性思维,开发人员如果能够深刻理解依赖倒置原则,将有很大层次的提升,还是看如下代码

<?php
/**
 * Class FileLog
 * @datetime 2020/7/12 6:09 PM
 * @author roach
 * @email jhq0113@163.com
 */
class FileLog
{
    /**
     * @var string
     * @datetime 2020/7/12 6:05 PM
     * @author roach
     * @email jhq0113@163.com
     */
    protected $_logFile;

    /**
     * @param $fileName
     * @datetime 2020/7/12 6:06 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function setLogFile($fileName)
    {
        $this->_logFile = $fileName;
    }

    /**
     * @param string $message
     * @param array  $context
     * @param string $leftPlace
     * @param string $rightPlace
     * @return string
     * @datetime 2019/8/30 18:00
     * @author roach
     * @email jhq0113@163.com
     */
    static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
    {
        if(empty($context)) {
            return $message;
        }

        $replace = [];
        foreach ($context as $key => $val) {
            $replace[$leftPlace . $key . $rightPlace] = $val;
        }
        return strtr($message, $replace);
    }

    /**
     * @param string $level
     * @param string $message
     * @param array  $params
     * @datetime 2020/7/12 6:07 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function log($level, $message, $params = [])
    {
        $message = self::interpolate($message, $params);
        $message = json_encode([
            'datetime' => date('Y-m-d H:i:s'),
            'level'    => $level,
            'msg'      => $message,
            'clientIp' => $_SERVER['REMOTE_ADDR'],
            'url'      => $_SERVER['REQUEST_URI'],
            'method'   => $_SERVER['REQUEST_METHOD'],
            'host'     => $_SERVER['HTTP_HOST']
        ], JSON_UNESCAPED_UNICODE);
        file_put_contents($this->_logFile, $message.PHP_EOL, FILE_APPEND | LOCK_EX);
    }
}

/**
 * Class DbLog
 * @datetime 2020/7/12 6:11 PM
 * @author roach
 * @email jhq0113@163.com
 */
class DbLog
{
    /**
     * @var \PDO
     * @datetime 2020/7/12 6:11 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    protected $_pdo;

    /**
     * @param \PDO $db
     * @datetime 2020/7/12 6:12 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function setDb(\PDO $db)
    {
        $this->_pdo = $db;
    }

    /**
     * @param string $message
     * @param array  $context
     * @param string $leftPlace
     * @param string $rightPlace
     * @return string
     * @datetime 2019/8/30 18:00
     * @author roach
     * @email jhq0113@163.com
     */
    static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
    {
        if(empty($context)) {
            return $message;
        }

        $replace = [];
        foreach ($context as $key => $val) {
            $replace[$leftPlace . $key . $rightPlace] = $val;
        }
        return strtr($message, $replace);
    }

    /**
     * @param string $level
     * @param string $message
     * @param array  $params
     * @return int
     * @datetime 2020/7/12 6:16 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function log($level, $message, $params = [])
    {
        $message = self::interpolate($message, $params);
        
        $stmt = $this->_pdo->prepare('INSERT INTO `logs`(`datetime`, `level`, `msg`, `clientIp`, `url`, `method`, `host`)VALUES(?,?,?,?,?,?,?)');
        
        $stmt->execute([
            date('Y-m-d H:i:s'),
            $level,
            $message,
            $_SERVER['REMOTE_ADDR'],
            $_SERVER['REQUEST_URI'],
            $_SERVER['REQUEST_METHOD'],
            $_SERVER['HTTP_HOST']
        ]);
        
        return $stmt->rowCount();
    }
}

/**
 * Class LogFactory
 * @datetime 2020/7/12 6:21 PM
 * @author roach
 * @email jhq0113@163.com
 */
class Logger
{
    /**
    * @param string $loggerName
    * @param string $level
    * @param string $message
    * @param array  $params
    * @return int|void
    * @datetime 2020/7/12 6:25 PM
    * @author roach
    * @email jhq0113@163.com
    */
    public function log($loggerName, $level, $message, $params = [])
    {
        if($loggerName === 'db') {
           $logger = new DbLog();
           $logger->setDb(new \PDO('mysql:host=127.0.0.1;port=3306;dbname=roach', 'roach', 'roach'));
           return $logger->log($level, $message, $params);
        }
                
        $logger = new FileLog();
        $logger->setLogFile('/tmp/logs/'.date('Y').'/'.date('m-d').'.log');
        return $logger->log($level, $message, $params);
    }
}

以上代码示例中,Logger类的log方法通过传入参数$loggerName来决定用哪个类去记录日志,这样Logger类直接依赖FileLogDbLog类,当我们再增加一个KafkaLog类打日志时,Logger同样需要依赖KafkaLog类,这样Logger类就需要发生修改,在模式领域,这样的代码认为不可以扩展,那么怎么优化呢?

<?php
/**
 * Class ILog
 * @datetime 2020/7/12 6:32 PM
 * @author roach
 * @email jhq0113@163.com
 */
abstract class ILog
{
    /**
     * @param string $message
     * @param array  $context
     * @param string $leftPlace
     * @param string $rightPlace
     * @return string
     * @datetime 2019/8/30 18:00
     * @author roach
     * @email jhq0113@163.com
     */
    static public function interpolate($message, array $context = [], $leftPlace='{', $rightPlace='}')
    {
        if(empty($context)) {
            return $message;
        }

        $replace = [];
        foreach ($context as $key => $val) {
            $replace[$leftPlace . $key . $rightPlace] = $val;
        }
        return strtr($message, $replace);
    }
    
    /**
     * @param string $level
     * @param string $message
     * @param array  $params
     * @return mixed
     * @datetime 2020/7/12 6:30 PM
     * @author roach
     * @email jhq0113@163.com
     */
    abstract public function log($level, $message, $params = []);
}

/**
 * Class FileLog
 * @datetime 2020/7/12 6:09 PM
 * @author roach
 * @email jhq0113@163.com
 */
class FileLog extends ILog
{
    /**
     * @var string
     * @datetime 2020/7/12 6:05 PM
     * @author roach
     * @email jhq0113@163.com
     */
    protected $_logFile;

    /**
     * @param $fileName
     * @datetime 2020/7/12 6:06 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function setLogFile($fileName)
    {
        $this->_logFile = $fileName;
    }

    /**
     * @param string $level
     * @param string $message
     * @param array  $params
     * @datetime 2020/7/12 6:07 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function log($level, $message, $params = [])
    {
        $message = self::interpolate($message, $params);
        $message = json_encode([
            'datetime' => date('Y-m-d H:i:s'),
            'level'    => $level,
            'msg'      => $message,
            'clientIp' => $_SERVER['REMOTE_ADDR'],
            'url'      => $_SERVER['REQUEST_URI'],
            'method'   => $_SERVER['REQUEST_METHOD'],
            'host'     => $_SERVER['HTTP_HOST']
        ], JSON_UNESCAPED_UNICODE);
        file_put_contents($this->_logFile, $message.PHP_EOL, FILE_APPEND | LOCK_EX);
    }
}

/**
 * Class DbLog
 * @datetime 2020/7/12 6:11 PM
 * @author roach
 * @email jhq0113@163.com
 */
class DbLog extends ILog
{
    /**
     * @var \PDO
     * @datetime 2020/7/12 6:11 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    protected $_pdo;

    /**
     * @param \PDO $db
     * @datetime 2020/7/12 6:12 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function setDb(\PDO $db)
    {
        $this->_pdo = $db;
    }

    /**
     * @param string $level
     * @param string $message
     * @param array  $params
     * @return int
     * @datetime 2020/7/12 6:16 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function log($level, $message, $params = [])
    {
        $message = self::interpolate($message, $params);
        
        $stmt = $this->_pdo->prepare('INSERT INTO `logs`(`datetime`, `level`, `msg`, `clientIp`, `url`, `method`, `host`)VALUES(?,?,?,?,?,?,?)');
        
        $stmt->execute([
            date('Y-m-d H:i:s'),
            $level,
            $message,
            $_SERVER['REMOTE_ADDR'],
            $_SERVER['REQUEST_URI'],
            $_SERVER['REQUEST_METHOD'],
            $_SERVER['HTTP_HOST']
        ]);
        
        return $stmt->rowCount();
    }
}

/**
 * Class LogFactory
 * @datetime 2020/7/12 6:21 PM
 * @author roach
 * @email jhq0113@163.com
 */
class Logger
{
    /**
     * @param ILog   $logger
     * @param string $level
     * @param string $message
     * @param array  $params
     * @return mixed
     * @datetime 2020/7/12 6:32 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function log(ILog $logger, $level, $message, $params = [])
    {
        return $logger->log($level, $message, $params);
    }
}

我们可以看到,在优化过程中,我们增加了ILog抽象类,并增加了抽象方法logLogger类中log方法不再依赖$loggerName,改为依赖抽象类ILog,这样,当我们增加KafkaLog类打日志时,Logger类不需要再做任何修改。

这就是典型的依赖倒置思想。

3.4 接口隔离

我们刚刚学会了依赖倒置,高层模块不应该依赖于低层模块儿,二者应该依赖其抽象。接口隔离告诉我们,这个抽象要有个度,应该建立在最小接口上。

看如下代码

<?php
/**
 * Interface IOrderAndLogin
 * @datetime 2020/7/12 6:45 PM
 * @author roach
 * @email jhq0113@163.com
 */
interface IOrderAndLogin
{
    /**
     * @param array $params
     * @return mixed
     * @datetime 2020/7/12 6:44 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function order($params = []);

    /**
     * @param array $params
     * @return mixed
     * @datetime 2020/7/12 6:46 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function login($params = []);
}

/**
 * Class ProductOrder
 * @datetime 2020/7/12 6:48 PM
 * @author roach
 * @email jhq0113@163.com
 */
class ProductOrder implements IOrderAndLogin
{
    /**
     * Order constructor.
     * @param array $params
     */
    public function order($params = [])
    {
        //各种下单操作
        return true;
    }

    /**
     * @param array $params
     * @return bool|mixed
     * @datetime 2020/7/12 6:48 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function login($params = [])
    {
        return false;
    }
}

/**
 * Class WxLogin
 * @datetime 2020/7/12 6:50 PM
 * @author roach
 * @email jhq0113@163.com
 */
class WxLogin implements IOrderAndLogin
{
    /**
     * @param array $params
     * @return bool|mixed
     * @datetime 2020/7/12 6:49 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function order($params = [])
    {
        return false;
    }

    /**
     * @param array $params
     * @return array|mixed
     * @datetime 2020/7/12 6:50 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function login($params = [])
    {
        // 各种操作
        return [
            'userId' => time(),
            'nickname' => uniqid()
        ];
    }
}

/**
 * Class OrderController
 * @datetime 2020/7/12 6:55 PM
 * @author roach
 * @email jhq0113@163.com
 */
class OrderController
{
    /**
     * @param IOrderAndLogin $order
     * @return mixed
     * @datetime 2020/7/12 6:55 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function orderAction(IOrderAndLogin $order)
    {
        return $order->order([
            'time' => time(),
        ]);
    }
}

以上代码我们可以看到,OrderController通过接口IOrderAndLogin依赖ProductOrder,但是IOrderAndLogin接口还有一个OrderController用不到的接口方法login,同时ProductOrder类根本不需要login方法,同样的,WxLogin类也不需要order方法,但是他们各自都必须去实现不需要的方法,这就不符合接口的单一职责原则。

这种优化思路很简单,把IOrderAndLogin接口拆分成IOrderILogin两个接口,ProductOrder类实现IOrder接口,WxLogin类实现ILogin接口,OrderController类依赖IOrder接口即可,由于代码很简单,不在提供实现细节。

3.5 迪米特法则

迪米特法则简单来说就是高内聚、低耦合,一个对象应该对其他对象保持最少的了解。

从耦合度来讲,继承>依赖,所以当我们使用继承的时候,除非是有明确的父子关系,否则不要乱用继承。

下面举一个简单的高内聚、低耦合的例子

<?php
/**
 * Created by PhpStorm.
 * User: Jiang Haiqiang
 * Date: 2020/7/12
 * Time: 4:09 PM
 */
class RedisCache
{
    /**
     * @var string
     * @datetime 2020/7/12 7:10 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $host;

    /**
     * @var int
     * @datetime 2020/7/12 7:10 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $port;

    /**
     * @var string
     * @datetime 2020/7/12 7:10 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $password;

    /**
     * @var int
     * @datetime 2020/7/12 7:10 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    public $db;

    /**
     * @var \Redis
     * @datetime 2020/7/12 7:11 PM
     * @author roach
     * @email jhq0113@163.com 
     */
    private $_instance;

    /**
     * @return \Redis
     * @datetime 2020/7/12 7:13 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function getRedis()
    {
        if(is_null($this->_instance)) {
            $redis = new \Redis();
            $redis->connect($this->host, $this->port);
            $redis->auth($this->password);
            $redis->select($this->db);
            $this->_instance = $redis;
        }
        return $this->_instance;
    }

    /**
     * @param string $key
     * @return bool|string
     * @datetime 2020/7/12 7:14 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function get($key)
    {
        return $this->getRedis()->get($key);
    }

    /**
     * @param string $key
     * @param string $value
     * @param int $timeout
     * @return bool
     * @datetime 2020/7/12 7:14 PM
     * @author roach
     * @email jhq0113@163.com
     */
    public function set($key, $value, $timeout = 60)
    {
        return $this->getRedis()->set($key, $value, $timeout);
    }
}

以上RedisCache封装类,也许大家在自己的项目中肯定看到过类似的代码,大家会觉得这有什么问题吗?

其实从高内聚低耦合的角度来讲,host,port,passworddb属性以及getRedis方法,对于其他类来说根本不需要知道,本着高内聚低耦合的思想,这些的访问权限至少要控制在protected级别,这样从技术角度避免了泄露和被修改的可能性。

3.6 开闭原则

开闭原则简单来说就是对修改关闭,对扩展开放,此原则理解起来稍复杂,我们可以狭义的这样理解。

如果我们的系统软件已经上线运行了,当发生新需求变化时,我们是通过增加代码实现来满足新需求而不是修改原来的代码来实现新需求,当然这是一个狭义的理解。

我们回头看依赖倒置的例子就是一个符合开闭原则的典型案例,当我们增加KafkaLog需求时,我们是通过增加KafkaLog类实现来满足需求,原有的FileLog类和DbLog类没有发生任何更改,这样就是一个典型的对扩展开放,对修改关闭的一个例子。

好了,设计模式的六大原则我们都通过案例学习完了,你都学会了吗?

学习更多内容: https://404.360tryst.com

我的视频课程: https://edu.csdn.net/course/detail/9933

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式 的相关文章

  • 云服务器搭建部署全流程

    本篇记录在centos7 3上部署web项目的全流程及一些问题的解决方案 工具准备 putty安装及使用 PuTTY可用来在windows上连接linux服务器 xff0c 可去PuTTY官网下载安装如果不想每次登录都输入密码 xff0c

随机推荐

  • 三种获取字节码对象的方式及区别

    方式一 xff1a 对象 getClass 方法是 根对象Object的方法 是其他类继承Object的getClass方法 方式二 xff1a 类名 class xff0c 你可以理解为字节码本身就是静态的 xff0c 类加载的时字节码就
  • 关于接口与Object 类的关系

    看到这个标题 xff0c 你或许就会想好自己的那份答案 但事实上这个确实没有答案 xff0c 至少没官方证明它们之间的基友关系 看法一 xff1a 因为老师说 xff0c 你可以把接口看作是特殊的类 xff0c 所以不假思索的就认为接口也
  • 单词博弈Java实现(借鉴“miss若尘”博客中写的解题思路)

    单词博弈Java实现 xff0c 已经通过庞果网的用例测试 代码如下 import java util HashMap public class WordGameFinalTest public static int who String
  • mysql安装时的粗心错误:last error unable to update security settings. access denied for user 'root' @ 'localh

    来自 梦想家haima 39 s blog gt http blog dreamwe cn 这个报错出现在mysql最后 当你看到mysql的最后一步需要设置密码可能你开心得很 Mysql就快安装好了 赶快输入三行密码都是root 结果报下
  • @SuppressWarnings

    简介 xff1a java lang SuppressWarnings是J2SE 5 0中标准的Annotation之一 可以标注在类 字段 方法 参数 构造方法 xff0c 以及局部变量上 作用 xff1a 告诉编译器忽略指定的警告 xf
  • 欢迎使用CSDN-markdown编辑器

    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来 xff0c 用它写博客 xff0c 将会带来全新的体验哦 xff1a Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传
  • Linux之强大的awk

    来自 梦想家 Haima s blog gt http blog dreamwe cn awk简介 awk是Linux中的一个命令 xff0c 用来做文本处理与分析 xff0c 功能简单强悍 xff0c 同时它也是一门编程语言 awk处理文
  • 手机抓包charles使用

    使用的是charles window 之前使过fiddler但是感觉并没有charles好用以及一目了然 链接 https pan baidu com s 1NMNXa8M4niLObQKIsCNL3A 提取码 2wsa 安装包可以通过连接
  • 安卓Tinker热更新接入踩坑(minSdkVersion 21)

    哎哟 xff0c 这个坑啊 我项目采用的是ARouter 43 Tinker 我接入的是tinkerpathchttp www tinkerpatch com Docs intro按照文档对接 xff0c 我采用的是reflectAppli
  • Android 解压和重新打包system.img

    开始我们的工作前 xff0c 请记住 xff0c Linux一定要学会用file命令分析文件类型 xff0c 这样才好入手 xff0c 否则错了都不知道怎么回事 xff01 xff01 xff01 1 解压system img 先用file
  • Jetpack系列学习笔记整理一 之LifeCycles

    学习最好的途径就是官网和github Demo xff0c 先放链接 xff0c 想看自行跳转 xff1a 官网 xff1a https developer android com topic libraries architecture
  • 获取安卓位置信息

    别忘了添加权限 xff1a lt uses permission android name 61 34 android permission INTERNET 34 gt lt uses permission android name 61
  • 安卓与html混合开发之原生与js相互调用

    原生和html的优缺点就不多说了 xff0c 有些特定条件下用html页面可以很方便 xff0c 也很容易更新和维护 xff0c 那么这就涉及到html与安卓原生的交互和通信 接下来我要分享的是html调用原生的弹窗和位置信息 xff0c
  • 应用保活--杀死进程也能收到推送消息

    我选取的是极光推送 xff0c 当把进程杀死时候就接受不到推送过来的消息 这是因为我使用的是小米手机 xff0c 小米和华为手机属于那种深度定制安卓系统 xff0c 需要用户的操作才能够实现应用 保活 的目的 小米 MIUI 自启动管理 x
  • 安卓原生与vue前段相互调用

    之前写过一个博客是安卓原生与JS交互的博客 xff1a http blog csdn net jhl122 article details 53406623 那是正常情况下的交互 xff0c 但是如果前段人员使用vue开发就会产生一个问题
  • CMake 编译时报错 ninja: error: ......missing and no known rule to make it

    Build command failed Error while executing process F Android sdk cmake 3 6 4111459 bin cmake exe with arguments build E
  • 数据类型和Json格式

    1 前几天 xff0c 我才知道有一种简化的数据交换格式 xff0c 叫做yaml 我翻了一遍它的文档 xff0c 看懂的地方不多 xff0c 但是有一句话令我茅塞顿开 它说 xff0c 从结构上看 xff0c 所有的数据最终都可以分成三种
  • golang语言rsa加解密及签名验签

    golang语言rsa加解密及签名验签 96 rsa 96 算法概述 96 Rsa 96 结构体封装封装的优点使用案例 rsa算法 概述 rsa是一种非对称的可逆的加密算法 xff0c 对加密数据长度有限制 xff0c 同时rsa也提供了数
  • 异常与错误处理

    异常与错误处理 PHP的异常与错误是分开的 xff0c 当程序出现异常时会throw一个 Exception 或子类 对象 xff0c 但是当出现错误时会触发一个错误 1 异常处理 1 1 通过try catch主动处理异常 span cl
  • 设计模式

    1 什么是模式 设计模式是对某些典型易变问题的特定解决方案 xff0c 这些问题和解决方案经过分类总结 xff0c 并且为了方便交流给每个解决方案都起了特定的名字 模式是为了解决变化的问题 xff0c 将变化的问题进行封装 xff0c 让变