预言需求——用好的设计,应对可能的变化

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/geyunfei_hit/article/details/99297305

预言需求——用好的设计,应对可能的变化

楔子

认识了一位艺术家朋友,在他的引导下,开始看一些艺术展,看各种油画,水墨,水彩————屡屡为在那种固化颜料之下的展现的波光粼粼而赞叹不已。

在另一位朋友的安利下,我迷上音乐,沉迷古典。

在流连这些艺术中时,我总在不停的感叹:好的艺术,一定是不朽的,我们可以听20年前的流行,欣赏200年前的古典,欣赏百十年前的毕加索。

每有这些感慨,我总自惭形愧,作为软件工程师,代码总是暂时的,短暂的,甚至从写下的那一刻起,就是过时的。

我们甚至在今天通宵去写一个只在明天运行一次的脚本。

是的,悲观的角度看,我们一直在做无用功

于是,我总会想,如何写出’不朽’的代码,让自己的工作,尽可能的完美,尽可能延长他的运行周期。

对我来讲,这种方式,应该是预见需求,即预期在一个时间内,你接到的需求去如何的变化,当这种变化发生时,你的设计去如何的应对。

是的,想一个预言家一样,去给你的代码预知一段时间内的未来。

好的工程师,都应该是预言家

那么今天这篇,就从一个非常小的需求,聊聊我是怎么去做预言家,给代码卜卦。

需求

这是我在之前的东家接到的一个需求,当时我们用php的laravel框架已经实现了后台服务并预期上线。

因为我们本质上是一个理财的网站,刚上线时效果不是很好,于是PM说,我们要给新注册的用户发券,促进他们消费。

是的,我们的需求很简单,在用户注册时,给用户发券

也就是说,需求是这个样子:

在这里插入图片描述

第一次实现

在当时,我们已经有了auth/user/register接口,里面调用了userService这个类的一个newUser的方法,这个方法里实例化了一个userData,它生成了一个userModel,并把它加到数据库中。
大概是这个样子:

userControler/register
|-userService/newUser
|--userData/newUser
|---userModel/create
|----mysql ...

那么直观的解决方式
我们只要有一个coupon的实现(data/model) ,然后在userServicenewUser方法里调用newCoupon就可以了。

于时我们代码这个样子就可以:

userControler/register
|-userService/newUser
|--userData/newUser
|---userModel/create
|----mysql ...
|--couponData/newCoupon
|---couponModel/create
|----mysql ...

可是,这样真的可以了么?
这个需求,会有什么变化?

深挖需求

之前说过,我们的需求是在用户注册时,送消费券
这个需求,有两个关键点,即什么时间————用户注册,做什么事情————送消费券

那么,从时间行为两个维度考虑后继可能发生的变化:

  1. 时间问题
  • 只在用户注册时发券么?
  • 用户登录时会不会发券?
  • 用户购买的时候会发券么?
  • 用户充值的时候会发券么?
  1. 行为问题
  • 我们只会发券么?
  • 我们有没有可能发现金?(理财体验金,你懂的)
  • 我们有没有可能加入邀请返现?

也就是说,我们的需求,有可能变成这个样子:

在这里插入图片描述

在想到这些可能到来的变化后,我不得不重新审视自己的设计……

进一步的考虑

在发现我的需求可能的变化后,我开始考虑,怎么去应对未来的变化,那么我的设计的目的变成了:

  1. 以一种通用的方式实现送券;
  2. 用合适的方式获知用户注册这个时间点;
  3. 尽可能简单的把1和2串联到一起;

救星:观察者

在猜想需求变化时,我意外的发现,laravel 中的model是有观察者的,即你可以定向的观察某个model(mysql)中的操作:
像这样,先定义一个observer :

namespace App\Observers;
use App\User;
class UserObserver
{
    /**
     * Handle the User "created" event.
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }
    /**
     * Handle the User "updated" event.
     * @param  \App\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        //
    }

    /**
     * Handle the User "deleted" event.
     * @param  \App\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        //
    }
}

然后,在serviceProvider里注册这个:

namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     * @return void
     */
    public function register()
    {
        //
    }
    /**
     * Bootstrap any application services.
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }
}

这正是标准的观察者模式,并且,重要的一点,我们在php是可以不用类型传参的(或是在java里Object)之类,所以,我们的objectServer 变成了:

class CommmonObserver
{
    
    public function created($item)
    {
        //
    }
    public function updated($item)
    {
        //
    }

    public function deleted($item)
    {
        //
    }
}

实现: 时机的问题

有了laravel底层的observer,那么用观察者模式捕获用户注册这个时间点就方便了:

class ServiceProvider
{
    /**
     * Bootstrap any application services.
     * @return void
     */
    public function boot()
    {
        User::observe(CommonObserver::class);
    }
}

如果我们的CommonObserver只给user这一个model用,那么这部分可以到此为止了,但是我们希望再有类似问题,都可以用类似的方式处理,在这种方式下,变成了:

class CommmonObserver
{
    
    public function created($item)
    {
        raise_event($item,'created');
    }
    public function updated($item)
    {
        raise_event($item,'updated');
    }

    public function deleted($item)
    {
        raise_event($item,'deleted');
    }
}

raise_event中,我们这样处理:

function raise_event($item,$event){
    //getName 
    $model_name = get_class($item);
    //map to event ..
}

这时,我们可以定义一个类似的array:

return [
    "App\User"=>[
        "created"=>"userRegister"
    ],
    "App\UserLoginLog"=>[
        "created"=>"userLogin"
    ],
    // other event base model 
];

这样可以得到这个array后,在raise_event中去将具体的事件生成;
这时,我们的需求,受设计影响成为了这样:
在这里插入图片描述

实现: 行为的问题

我们解决了时机,那么行为就变得容易了。
根根上文,不难看出,我们在实现行为时可以得到三个参数:

  1. event ---- 我们定义的事件,比如userRegister
  2. db_operation ---- db的操作,比如update,create
  3. data_item ---- 对应的数据,比如user的实体。

注意一点,其实在进行后继业务处理时,我们不用去关注db_operation这个参数,因为其实event确定的话,db_operation就确定了。

因为我们不能只关注送券这一件事情,所以,我们要有一个约定(inteface)去承接相应的事件处理;

interface IEventHandler{
    function handle($event,$item);
}

然后可以实现一个AddCouponHandler:

class AddCouponHandler{
    public function handle($event,$item){
        //相关的操作
    }
}

那么,怎么关联到一起? 哈哈,嗯,如你所愿,我们再来个array:

return [
    "userRegister"=>[
        AddCouponHandler::class,
        //other handler
    ],
    "userLogin"=>[
        //other handler
    ],
    //
];

这样,我们的设计最终变成了这个样子:
在这里插入图片描述

理想主义的形式

我们在这个需求的第一版的迭代上,加入了这种设计,然后随着后继的迭代,我们又做了如下的更改:

  1. 对要发送的model进行处理,将db的原始数据进行裁剪,形成了统一的数据约定;
  2. 将event的处理变成了异步。

这时,我们的设计就成为了这种:

在这里插入图片描述

小结

我们将一个看似简单的需求————注册送券,无限的复杂化,去预测这个需求可能发生的迭代,可能产生的变化。
在这里,我们使用观察者模式,实现了对现有代码的扩展,同时,运用了大量的工厂模式(array).
在保证实现基础的基础上,我们做到了:

  1. 对原有代码实现了非侵入的扩展(user相关的内容没有更改)。
  2. 加入的所更改都是以增加为主;
  3. 新的业务逻辑是低耦合甚至完全解耦的。
  4. 为以后可能的变化留出了充足的时间。
展开阅读全文

比较复杂的需求可能需要正则

10-14

遇到难题。。。请见后文详述:rnrn[b]文本文档abc.txt中的内容格式如下:[/b]rnrnSF ArnXM 张三;李四;王五rnDZ 广东省广州市天河区rnSR 低保rnLX 老年人;青年人;尚未成年;老年;中年rnEDrnrnSF BrnXM 程真;欧阳晓兰rnDZ 广东省珠海市香洲区吉大rnSR 离退休rnLX 老年人;中年人rnEDrnrnSF ArnXM 吴晓光rnDZ 广东省珠海市香洲区吉大rnSR 离退休rnLX 老年人;中年人;残疾rnEDrnrnSF CrnXM 张德良rnDZ 广东省中山市五桂山镇rnSR 工作中rnLX 老年人rnEDrnrn[b]文档说明:[/b]rnrn1、SF、XM等均位于本行的最前面。rn2、XM、LX,这两行,有时候有分号,有时候没有分号。rnrn[b]需求:[/b]rnrnXM那一行,如果遇到“;”,“;”后的词语换行;XM那一行如果没有“;”,则保持不变;除了XM那一行之外,其他所有行内容都不变。rnrnrn[color=#FF0000][b]希望得到的结果是:[/b][/color]rnrnSF Arn[color=#0000FF]XM 张三rn李四rn王五[/color]DZ 广东省广州市天河区rnSR 低保rnLX 老年人;青年人;尚未成年;老年;中年rnEDrnrnSF Brn[color=#0000FF]XM 程真rn欧阳晓兰[/color]DZ 广东省珠海市香洲区吉大rnSR 离退休rnLX 老年人;中年人rnEDrnrnSF Arn[color=#0000FF]XM 吴晓光[/color]DZ 广东省珠海市香洲区吉大rnSR 离退休rnLX 老年人;中年人;残疾rnEDrnrnSF Crn[color=#0000FF]XM 张德良[/color]DZ 广东省中山市五桂山镇rnSR 工作中rnLX 老年人rnEDrnrnrn这个有点麻烦,因为LX那一行,有时候也有分号“;”。所以我想,用正则判断XM行,是否带有分号,有的话,则换行;没的话,则不变。大大们有什么好的解决方法呢? 论坛

应对大数据上SQL的需求:Apache Drill

08-25

[size=14px][align=center]应对大数据上SQL的需求:Apache Drill[/align][/size]rnSQL已经相当流行——为什么呢?客户正在寻求交互式的大数据解决方案,这些解决方案需要流线型工作流和选择便利性。能够在Hadoop和其他大数据系统上使用SQL,已经朝着这一目标迈了一大步。rnrn产生这种需求的一个原因是,这些大数据工具可以与SQL交互,但是以往的大数据解决方案并不能。上世纪70年代IBM研究院Ted Codd开发了SQL,因为人们需要一个标准方式来访问和使用关系型数据库中的数据。这需求仍然存在,它甚至比以前更重要,因为目前很多系统已经实现生成标准的SQL。然而,对于Hadoop这样的现代可扩展系统和非关系型数据库,标准事务型SQL不再适用,所以,系统分离了。这种不当的搭配可能意味着冗余、昂贵和复杂的工作区,来满足SQL兼容基于Hadoop大数据系统的低成本优势的广泛需求。rnrnMapR技术通过多种方式解决这些问题。它通过自己的大数据平台和对开源项目Apache Drill的贡献来提供广泛的支持。rnrn多达30种新产品和开源项目试图解决Hadoop上SQL和类SQL的需求,包括Apache Hive,Cloudera的Impala,开源Apache Drill,通过Cascading开发的MapReduce和Hadoop的开源SQL解决方案Lingual。rnrn[b]Apache Drill是什么?为何MapR对它投入如此之多?[/b]rnrn客户想要很广泛的功能,而在引入新技术的时候,Apache Drill的设计使得它能够很容易就连接到大范围的分析工具和数据源。rnrn很多Hadoop项目上的SQL都是把小数据集上开发的功能再次开发,尽力让它们满足大数据的需求。虽然它们解决了很多真实的需求,但是它们根本上还是后视镜型项目。相反,Apache Drill作为一个媒介,将新技术引入这个问题域。Apache Drill受到google的Dremel项目的影响,达到了更高的要求,并且正在设计新的功能。rnrnApache Drill提供了访问大数据存储的交互式特定查询功能。Drill的一个重要特征就是速度,它被设计在低延迟响应下处理P字节的数据。Drill很重要的一个方面是,它不解决过去5-10年的问题,而是向前建立一个新的技术,解决当前和未来5年内的需求。rnrnDrill高灵活度的架构设计主要提供如下关键技术:rn1模式可选rn2处理嵌套数据的能力(例如JSON,Protobuf,Parpuet)rn3柱形内存存储和执行rn4全标准的ANSI SQL:2003查询能力rn5先进的低成本优化器rn6为多个社区提供广泛好处的高可扩展的架构(例如,向非SQL PIG的扩展能力,或者建立机器学习原语,能够集成到Drill为Mahout提供先进执行引擎)rn7YARN整合rnrnApache Drill开源项目的社区驱动方面相当重要。除了MapR的支持,Apache Drill的贡献者来自不同的地区和公司,包括Pentaho,Oracle和VMVare等。Drill开发者一直合作产生大量的代码,准备alpha版本的发布。随着这些新技术将传统工具和现代基于Hadoop的系统连接起来,我们正在进入一个大数据分析和大规模机器学习的令人激动的时期。rnrn来自[url=https://www.mapr.com/blog/responding-to-the-need-for-sql-on-big-data-apache-drill#.U_shFXWSzCI]Responding to the Need for SQL on Big Data: Apache Drill[/url] 论坛

没有更多推荐了,返回首页