工作阶段性总结

到杭州工作快三个月了,一直跟随着公司的快节奏,很少让自己停下来总结总结。积累的多了,若是不总结,这些积累的东西可能也会随着时间慢慢丢失了吧…

在公司做的工作内容与我自己所写的项目大致相同,公司的项目是前后端分离,我只需要关心后端的接口服务,偶尔抽个包之类的,这些事情做起来不说游刃有余,倒也没遇到多少瓶颈,还算是挺顺利的,也很有激情。激情是因为通过工作的内容,我能学到很多技术优秀的用法,比我曾经用的好得多的。

技术

async包,我以前是没有用过的。异步的机制在node来说是再正常不过的事了,而我也就简单的用用它的回调,感觉好像就这样了,其实我不知道的还有很多。async包能够将运行效率成倍的提升,例如其中的parallel、map等方法,有段时间,我在工作中疯狂的用这些方法,真入迷…但是这个包诱发的是我对其原理的兴趣,我想了解这些功能是如何实现的,因此,我在慢慢的啃源码…

测试。关于javascript的测试,自己也有用过,使用jasmine来写的。到公司之后,接触了should、supertest、mocha、istanbul等包,这一套工具对测试来说,以我目前的水平已经足够用了。在践行测试先行之后,能将这一套工具初步的用起来,我的接口的测试代码均在500-600行左右,多的能有1000多行。为什么能写那么多行,除了基本的结果数据、边界、权限等,主要是造数据的代码太多了。没有忘记熊节老师的提点(写了这么多重复代码你也好意思- -),将造数据的代码提取了方法,获取登陆状态的也进行了封装,能精简的就精简,但还不够自动化…感觉这三个月,加上测试代码,妥妥的有一万行代码了…测试这块,我很想再提高的,想再精简一点,能自动化就最好了,得多逛逛github吧~

健壮性。这个词,在龙湖的时候是经常提起的,健壮性、可读性、可靠性…而实际的工作中,程序出了bug崩溃了,才让我真正明白了何谓健壮性。nodejs作为单线程的语言,一旦有一处调用出问题了,那么整个程序便会挂掉。举个工作中的例子吧,比如,前台一个请求,需要一个数据列表,我收到这个请求之后去数据库查找,找到相关数据之后,为了显示的完整行,可能还需要返回这些数据的创建者信息,那么我就用这些数据存储的创建者的id去查找然后在返回的结果中给创建者信息赋值。这个过程中,假如某条数据没有存储创建的id,或者这个id对应的创建者不存在了,那么你调用data.creater/user.name,就会报错。随之而来的就是程序整个挂掉。正式运营的项目挂掉的严重性我就不提了,就说说怎么处理这些脏数据所造成的威胁吧。目前来说,我有三种处理方式:

  1. 在你觉得可能会出现问题的代码那里加上try catch块,在catch/finally中对其做处理
  2. 直接给个默认值,比如data.creater = data.creater || ''
  3. if条件判断

处事

除技术方面之外,在做事这块,也有很多体会(大多时候是被喷了థ౪థ)。

公司的项目是前后端分离,那么上线之前得有个联调阶段,这之间就可能会出一系列问题…比如,接口文档的字段写错了,前后端可能一方用正确的字段,一方可能不改,联调的时候就会出现

“诶,你这少返回了一个字段啊喂” 
“没有啊,我测试过了的” 
“...你来看看”
“我靠,你这字段错了,明显单词拼错了嘛”
“你看文档,我们按文档来好吧”
“文档定错了我们也按文档来?balabalbala”
“...”

比如,前后端联调都过了,

“兄弟,你那边显示没问题了吧“
”没问题,我都看过了,数据有了“
”那行,棒棒哒~“
”...“

快上线的日子到了,

”那孩子,这接口是你写的吧,测试过了没“
”没问题的,老大,我和前端测过了,ok的“
”那你来看看数据怎么才10条!!“

蹬蹬蹬的跑过去,打开console,看了看返回数据,

”诶,你看,我这分页的数据有传给他啊“
”那当时联调的时候怎么没解决呢?这是你做的东西,你不跟进确认吗?
你做的东西被糟蹋了你还淡定的跟我说你传了?是,前端是有责任,那你呢?balabalabala“

感觉自己都要被喷死了…实话说,这一天我的状态是崩溃的థ౪థ

成长的过程是痛苦的,我觉得我一定是成长了很多,这也是我这天唯一能高兴的点…

这个故事告诉我,

  1. 自己做的东西一定切实的确认它成功的落地了,也就是说不仅要保证自己这边的东西没有bug,还得确认调用的人那边也没有问题,这算是对自己负责吧。
  2. 做事情,一定得和同事保证信息的畅通性、实时性,有什么问题、哪里有改动,需要通知的一定得互相知会到位。

然后呢,我觉得应该总结出一个最重要的事,

一有什么问题浮现在脑海里马上就不假思索的提出来,这并非是件对我们有帮助的事情。

有时候,坑是自己挖的,自己掉进去了,也只有自己默默地爬了(含泪总结థ౪థ)…

总结展望

总的来说,这三个月过的还行(至少没找家里要钱),三思而后行,三思而后问,谋定后动。接下来,这些总结的东西需要学而时习之,最后还是对接下来的事情定个计划:

  1. 继续研究async包,尽力搞懂其原理
  2. 继续研究大文件读取写入(这个是自己某次偶然遇到),对node的文件操作有自己的心得
  3. 不辜负豪情大大的提点,在前端方面还是要自己多努力,做一个自己demo
  4. 对express框架进行更深入的理解

加油,baby

Share Comments

Linux学习之文件操作

The Linux Command Line 学习翻译

mkdir

The mkdir command is used to create directories.It works like this:

mkdir命令是用来创建目录的,这样使用:

mkdir directory…

A note on notation: When three periods follow an argument in the description of a command(as above),it means that the argument can be repeated,thus:

注意表示法:当遇见一个后面有三个圆点的命令(如上所示),这表示那个参数可以重复:

tmp mkdir max
➜ tmp ls
a.json max server.js

➜ tmp mkdir max1 max2 max3
➜ tmp ls
a.json max1 reade_file.js
clone_obj.js max2 server.js
getIp.js max3

cp

The cp command copies files or directories. It can be used two different ways:

cp命令,复制文件或目录,它有两种使用方法:

cp file1 file2

to copy the single file or directory “file1” to file or directory “file2” and :

复制单个文件或目录,和:

cp file… directory

to copy multiple files(either files or directories)into a direcotory.

复制多个文件或目录到一个目录下。

mv

The mv command performs both file moving and file renaming, depending on how it is used. In erther case,the original filename no longer exists after the operation. mv is used in much the same way as cp:

mv命令可执行文件移动以及文件重命名两种任务,这依赖于如何使用它。任何一种情况下,在此操作之下原始文件都将不再存在。mv命令使用方法同cp一样:

mv file1 file2

mv file1 file2 file3 code

rm

The rm command is used to remove(delete) files and directories:

rm命令用来删除文件或者目录:

rm file…

Options

Option meaning
-r –recursive recursive operation
-f –force force operation

ln

The ln command is used to create either hard or symbolic links. It is used in one of two ways:

ln命令用来创建硬链接,也可以创建符号链接。可以用其中一种方法使用它:

ln file link

to create a hard link, and:

创建硬链接,和:

ln -s item link

to create a aymbolic link “item” is erther a file or a directory.

创建符号链接,“item”可以是一个文件或是一个目录。

Hard links are the original Unix way of creating links, compared to symbolic links, which are more modern. By default, every file has a single hard link that gives the file its name. When we create a hard link, we create an additional directory entry for a file. Hard links have two important limitations:

硬链接最初是Unix创建的一种链接方式,和符号链接比起来,而符号链接更加现代.在默认方式下,每个文件有一个硬链接,这个硬链接就是文件的别名。当我们创建一个硬链接时,我们也就为这个文件创建一个额外的入口。硬链接有两个重要的局限性:

  1. A hard link cannot reference a file outside its own file system. This means a link may not reference a file that is not on the same dis partition as the link itself.
  2. A hard link may not reference a directory.

  3. 一个硬链接不能关联一个它所在文件系统之外的文件。这是说一个链接不能关联与链接本身不再同一个磁盘分区上的文件。

  4. 一个硬链接不能关联一个目录。

Symbolic links were created to overcome the limitations of hard links. Symbolic links work by creating a special type of file that contains a text pointer to the referenced file or directory.In this regard, they operate in much the same way as a Windows shortcut though of course, they predate the Windows feature by many years.

符号链接的创建是为了克服硬链接的局限性。符号链接通过创建一个包含指向关联的文件或目录的文本指针的特别的文件类型来工作。在这一方面,他们和Windows的快捷方式差不多,当然,符号链接早于Windows的快捷方式很多年。

A file pointed to by a symbolic link, and the symbolic link itself are largely indistinguishable from one another. For example, if you write some something to symbolic link, the referenced file is also written to. However when you delete a symbolic link, only the link is deleted, not the file itself. If the file is deleted before the symbolic link, the link will continue to exist, but will point to nothing. In this case, the link is said to be broken. In many implementations, the ls command will display broken links in a distinguishing color, such as red, to reveal their presence.

一个符号链接指向一个文件,而这个符号链接本身与其他的符号链接几乎没有区别。比如,如果你向一个符号链接里写入一些东西,那么关联的文件也会被写入。然而当你删除一个符号链接时,仅仅这个链接被删除,不会影响到文件本身。如果这个文件在符号链接之前被删除了,这个链接将会继续存在,但不会指向任何东西。这种情况下,这个链接被称为坏链接。在许多实现中,ls命令会以可区分的颜色来显示这些坏链接,比如红色,来显示他们的存在。

Share Comments

Linux学习之探索文件系统

The Linux Command Line 学习翻译

ls

With it, we can see directory contents and determine a variety of important file and directory attributes.

通过它,我们可以看见目录的内容、重要的文件和目录属性

~ ls
Applications Downloads Music VirtualBox VMs company
Desktop Library Pictures build doc
Documents Movies Public code system
➜ ~

Besides the current working directory,we can specify the directory to list,like so:

除了当前工作目录外,还可以指定别的目录,像这样:

ls ./code
N-chat crawl-info muying npm_ref_wf_test test
cn_admin_backend geoip-cn node-geoip npm_test999 tmp
cn_backend mail_test nodeclub personal zhongzhong
➜ ~

Or even specify multiple directories. In this example we will list both the user’s home directory(symbolized by the “~” character) and the code directory:

甚至可以列出多个目录的内容,在这个例子中,将会列出home目录和code目录的内容:

~ ls ~ ./code
./code:
N-chat crawl-info muying npm_ref_wf_test test
cn_admin_backend geoip-cn node-geoip npm_test999 tmp
cn_backend mail_test nodeclub personal zhongzhong

/Users/shining3d-fyqj:
Applications Downloads Music VirtualBox VMs company
Desktop Library Pictures build doc
Documents Movies Public code system
➜ ~

Most commands use options consisting of a single character preceded by a dash, for example, “-l”, but many commands, including those from the GUN project, also support long options, consisting of a word preceded by two dashes. Also, many commands allow multiple short options to be strung together. In this example, the ls command is given two options, the “l” options to produce long format output, and the “t” option to sort the result by the file’s modification time.

大多数命令使用的选项是使用一个中划线加一个字符组成,例如“l”,但是许多命令,包括来自于UGN项目的命令,也支持长选项,长选项是由两个中划线加一个单词组成。当然,许多命令允许使用多个短选项串一起使用。下面这个例子有两个选项,“l”产生长格式输出,“t”得到按修改时间的先后顺序排序的结果。

➜ ~ ls -lt
total 0
drwx——+ 50 shining3d-fyqj staff 1700 10 28 11:13 Downloads
drwxr-xr-x 8 shining3d-fyqj staff 272 10 27 10:38 company
drwxr-xr-x 4 shining3d-fyqj staff 136 10 26 17:20 VirtualBox VMs
drwxr-xr-x 4 shining3d-fyqj staff 136 10 26 16:44 system
drwx——@ 53 shining3d-fyqj staff 1802 10 26 16:26 Library
drwxr-xr-x 18 shining3d-fyqj staff 612 10 26 14:03 code
drwx——+ 5 shining3d-fyqj staff 170 10 21 11:07 Pictures
drwxr-xr-x 9 shining3d-fyqj staff 306 10 20 16:35 doc
drwxr-xr-x 4 shining3d-fyqj staff 136 10 20 14:41 build
drwx——+ 4 shining3d-fyqj staff 136 10 16 09:14 Music
drwx——+ 5 shining3d-fyqj staff 170 9 24 15:24 Desktop
drwx——+ 10 shining3d-fyqj staff 340 9 21 14:55 Documents
drwx—— 4 shining3d-fyqj staff 136 8 17 10:31 Applications
drwx——+ 3 shining3d-fyqj staff 102 8 17 10:26 Movies
drwxr-xr-x+ 5 shining3d-fyqj staff 170 8 17 10:26 Public
➜ ~

We’ll add the long option “–reverse” to reverse the order of the sort:

得到的结果中还有delete权限的信息

➜ ~ ls -lt -reverse
total 0
0 drwxr-xr-x+ 5 shining3d-fyqj staff 170 8 17 10:26 Public
0: group:everyone deny delete
0 drwx——+ 3 shining3d-fyqj staff 102 8 17 10:26 Movies
0: group:everyone deny delete
0 drwx—— 4 shining3d-fyqj staff 136 8 17 10:31 Applications
0 drwx——+ 10 shining3d-fyqj staff 340 9 21 14:55 Documents
0: group:everyone deny delete
0 drwx——+ 5 shining3d-fyqj staff 170 9 24 15:24 Desktop
0: group:everyone deny delete
0 drwx——+ 4 shining3d-fyqj staff 136 10 16 09:14 Music
0: group:everyone deny delete
0 drwxr-xr-x 4 shining3d-fyqj staff 136 10 20 14:41 build
0 drwxr-xr-x 9 shining3d-fyqj staff 306 10 20 16:35 doc
0 drwx——+ 5 shining3d-fyqj staff 170 10 21 11:07 Pictures
0: group:everyone deny delete
0 drwxr-xr-x 18 shining3d-fyqj staff 612 10 26 14:03 code
0 drwx——@ 53 shining3d-fyqj staff 1802 10 26 16:26 Library
0: group:everyone deny delete
0 drwxr-xr-x 4 shining3d-fyqj staff 136 10 26 16:44 system
0 drwxr-xr-x 4 shining3d-fyqj staff 136 10 26 17:20 VirtualBox VMs
0 drwxr-xr-x 8 shining3d-fyqj staff 272 10 27 10:38 company
0 drwx——+ 50 shining3d-fyqj staff 1700 10 28 11:13 Downloads
0: group:everyone deny delete
➜ ~

file

As we explore the system it will be useful to know what files contain. To do this we will use the file command to determine a file’s type. We can invoke the file command this way:

随着探索文件系统的进行,知道文件信息是很有用的,可用file命令达此目的。我们可以这样调用:

➜ tmp file server.js
server.js: UTF-8 Unicode text

Share Comments

New Begin

时隔半年之久,终于,我又拾起了blog。


自去年十月份开始正式走上程序员这条道路起,学习也一年有余。由一次意外得到的消息加入Thoughtworks的培训项目– Thoughtworks Academy,在这里,自学两月javascript、html、css等前端基础,然后在仝老师、玉姐、硕哥等的辅导下,接触互联网技术的最前沿思想,学习Thoughtworks的软件制作流程、TDD开发模式、团队合作,收获了很多…这段日子让我能够独立的去面对新技术而游刃有余,时常想念这段美好的日子,好想你们啊~

接着便是大四去公司完成实习任务,但在这个公司用的却是java,做的工作也是后端sql维护等,同事相对来说没有那种学习的朝气,与我当初进入这个领域所接受的思想完全不符,不开心,而作为程序员来说,不开心意味着就不会长久。因此,在七月底的时候,舍弃了很多,克服了很多,还是坚持了自己的选择,离开公司,去远方,做自己想做的工作。我觉得,男人就是该拼一拼,哈哈

杭州,初来乍到,请多指教。一个人来到杭州,诸多不便,但是在公司的工作却让我动力十足,做着自己喜欢的事情,几乎感觉不到疲惫。每晚八九点下班回家还能再写写自己的项目,那股兴奋劲自己都害怕,虽然时常顶着个黑眼圈,哈哈哈

blog很久没开更了,这段时间做了很多东西,也没来得及总结,就趁着这篇New Begin,慢慢的沉淀吧~

Let’s go!

Share Comments

设计模式之代理模式

该篇讲主要讲述我学习代理模式的心得记录。

代理模式基本属性

1、定义

一个类可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。而代理又分为静态代理和动态代理,其中动态代理具有更强的灵活性,不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,而是把这种实现推迟到程序运行时。

2、应用场景

功能需要被授予不同的权限时,如,注册与未注册的用户。

3、优点

一个复杂对象可以由多个代理来引用,这样便节省了系统资源。

实例讲解

1、静态代理

首先定义一个抽象类AbstractObject并在其内部定义一个抽象的方法,作为代理和被代理类的父类,

 public abstract class AbstractObject {
  public abstract void operation();
}

,然后创造一个继承了此父类的被代理类,

public class RealObject extends AbstractObject {
  @Override
  public void operation(){
      System.out.println("some operation");
 }
 }

再创造一个代理类,

 public class ProxyObject extends AbstractObject {
   RealObject realObject = new RealObject();
 @Override
     public void operation() {
       System.out.println("before");
       realObject.operation();
       System.out.println("after");
   }
}

在代理类中创建了一个父类,并且在重写的方法中调用了被代理类的同名方法。这便实现了一个简单的静态代理。

回顾一下思路,它实现一个共有的抽象类,代理与被代理均继承于这个类,在代理类中构造被代理类作为一个私有成员,并且在重写的方法中调用被代理类中重写的方法,并且可在该方法中添加自己的行为,最后客户端调用代理类中他们的共有接口,实现代理操作。

感觉和装饰者模式有些类似,均实现了相同的接口,通过调用底层的类来实现他们的功能。不过装饰者模式是将被装饰者作为参数传入,而静态代理是作为自己的私有成员变量。

2、动态代理

我们采用定义接口的方式来实现,首先定义一个接口AbstractSubject,

 public interface AbstractSubject {
  public abstract void request();
}

接着就是我们被代理的对象,

public class RealSubject implements AbstractSubject {
 @Override
 public void request()
 {
     System.out.println("real subject's request()........");
 }
}

在被代理类中我们重写了接口中定义的方法,接着再实现最重要的代理类,

public class DynamicProxy implements 
InvocationHandler {
     Object beProxy = null;
 public DynamicProxy(Object beProxy){
     this.beProxy = beProxy;
    }
     @Override
   public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
       System.out.println("before ----------"+method);
      Object result = method.invoke(this.beProxy,objects);
      System.out.println("after ----------"+method);
        return result;
  }
}

在这个代理类中,我们有一个需要传入被代理类对象的构造方法,而它实现的接口是系统自带的InvocationHandler,并且重写了invoke()方法。最后,我们来看一下,我们是怎么使用这个代理的,

  public class DynamicProxyClient {
    public static void main(String[] args) {
        AbstractSubject realSubject = new RealSubject();
        ClassLoader loader = realSubject.getClass().getClassLoader();
      Class<?>[] interfaces = realSubject.getClass().getInterfaces();

     InvocationHandler dynamicProxyHandler = new DynamicProxy(realSubject);

AbstractSubject proxy = (AbstractSubject) Proxy.newProxyInstance(loader, interfaces, dynamicProxyHandler);
        proxy.request();
    }
 }

    
使用java原生的Proxy类与InvocationHandler接口,通过自己所写的DynamicProxy实现InvocationHandler接口,在程序运行时,newProxyInstance()方法通过接收类加载器ClassLoader,动态代理类所实现的接口interface以及实现了InvocationHandler接口的动态类实例DynamicProxy,创建一个动态代理proxy。

其中,在DynamicProxy类中重写的invoke()方法,则是动态代理所必须执行的,因为Proxy.newProxyInstance()方法返回的是一个继承于Proxy的子类对象,通过调用该对象的代理方法,会执行父对象中InvocationHandler成员的invoke()方法,因此,最终执行了我们在DynamicProxy中重写的invoke()方法。关于最后proxy调用request()方法为什么就能执行到被代理类的方法,这里我再做一次说明。因为proxy是属于Proxy类的,因为他的类是com.sun.proxy.$Proxy0,是继承了Proxy类的,而在$Proxy0中重写了request()方法,并且调用了父类中h的invoke方法,而在父类Proxy中,h是这样定义的,

protected InvocationHandler h;

也就是说调用的是InvocationHandler.invoke()方法,而DynamicProxy又实现了InvocationHandler接口的invoke()方法,因此,最后调用的便是DynamicProxy中的invoke()方法。这也就是java内置的动态代理实现机制。

总结

学习了该模式之后,有了些感悟。设计模式,究其本质,就是一些方式方法,并且是适合面向对象这种语言的方法,我们通过这些方法能够顺利的达到我们所需要的目的,并且能够让我们的代码结构变得更清晰,更具逻辑。这便是设计模式的目的吧。

Share Comments

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Share Comments

设计模式之单例模式

各个模式均有自己的出现的理由以及应用场景,相较于普通代码有一定的优势的时候的,设计模式便也应运而生了。今天所要记录的单例模式,它的优势在哪呢?

单例模式简历

1、出生原因

当我们在一个管理者管辖范围不明确,各个部门间的领导互相插手彼此部门间的事物,员工一会被这个boss分配任务,一会被那个boss分配任务的公司里,作为一个普通员工,您怎么想?对于一个管理层这样混乱与模糊的公司,您怎么看?在一个项目中也一样,我们的某些类,一会被这个实例化,一会被那个实例化,而这个类的功能却是很单一的管理着同样一个领域,这个时候,你会不会想着做点什么来结束这个混乱的现象呢?重构、优化一下吧,特定的领域管理交由一个固定的对象来管理,那么,这个时候,单例模式就出现了。

2、定义

单例对象的类必须保证只有一个实例存在,通过阻止外部实例化和修改,来控制所创建的对象的数量。

3、适用情况

整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

4、实现思路

一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

5、亮点

整个系统中只有这么一个实例对象,方便各处的运用,也方便管理该对象所要管理的东西。

实例讲解

接下来,我们通过三个不同层次的单例模式实例,来较为深入的理解在java代码中如何实现及其原理。

首先,在我们的level1包下面创建Singleton类,

public class Singleton {
  private static Singleton instance  = null;
   public static Singleton getInstance(){
       if(instance == null){
          instance = new Singleton();
      }
       return instance;
   }
    private Singleton() {}
    public void doSomething(){
      System.out.println("'how are you'");
  }
}

将Singleton的构造函数私有化,阻止外部实例化,再通过调用一个共有的接口getInstance()方法,首先if(instance == null)判断其是否创建,再返回instance对象。这一切看起来都还挺不错的,一环接一环的保证了咱们Singleton类只返回一个实例化对象instance。可是真的是这样吗?让我们的大脑跑起A、B双线程来,假如当A准备创建instance的时候B正好进行if()判断语句,这个时候instance == null 是要返回true的,那么,这种情况肯定就会创建两个instance对象了,所有,这个实现方法在多线程里,是不安全的单例模式,或者说多线程情况下,这种实现不合理。

那么,开动我们的大脑,想想办法来控制他的实例化判断。于是,会写出如下代码:

 public class Singleton {
   private static Singleton instance = null;
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class) {                    if (instance == null) {
        instance = new Singleton();
                 }
        }
      }
      return instance;
  }
    private Singleton() {
  }
  public void doSomething() {
      System.out.println("'how are you'");
  }
}

我们通过double-checked-locking的方法来阻止其多线程运行。当getInstance()方法被调用的时候,首先进行null值检测,若是false直接返回instance,否则,运行同步锁,(synchronized,它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码),然后再进行null值检测,返回instance。这样,我们的程序就没有多线程情况下被多次创建的可能。感觉,程序写到这里,已经是很酷了,其实我也觉得,哈哈。不过,当我们了解创建一个变量的步骤时,可能就会有疑问了。

创建一个变量,有以下几个步骤:申请一块内存,调用构造方法进行初始化,分配一个指针指向这块内存。而这些都是JVM来执行,但是JVM仅仅是一个标准,没有规定编译器优化的内容,导致其可以自由进行优化,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。那么,就有可能会出现这种情况:当A线程开始创建Singleton实例时,B线程调用getInstance()方法检测null时,假如此时A已经将instance指向了那块内存,但还没有赋值,那么B线程检测null时会直接返回instance,但是instance的构造并没有完成,程序便出错了。

这可让我们很是懊恼,怎么感觉处处碰壁的样子,难道JAVA就不能安全的实现单例模式吗?结果肯定是有的,平下心来,我们来看看接下来的实现:

public class Singleton {
   private Singleton() {
   }
     private static class SingletonInstance {
       private static final Singleton instance = new Singleton();
   }
     public static Singleton getInstance() {
      return SingletonInstance.instance;
  }   
  public void doSomething() {
      System.out.println("'how are you'");
  }
}

使用java静态内部类,JVM能够保证当一个类被加载时,这个加载过程是互斥的。调用getInstance()方法时,首先加载SingletonInstance类,这个类有一个static实例,所以需要调用Singleton的构造方法,最后getInstance将这个instance返回给使用者。

总结

通过学习单例模式之后,感觉学习一门语言,仅仅浅显的学习是不够的,有时候,你仅仅知道怎么用,你可能永远都写不出好的程序,这不是危言耸听。据我所知,优秀的程序员,几乎都学习过编译原理,当然,信息来源于知乎,因为自己平时知乎浏览的要多一些。所以,编译原理,已经被我提上今后的日程表了。

设计模式这一块,可能得暂时停下来了,因为有了新的任务,不过剩下的一篇代理模式我会抽空更新的,这是我的承诺@杜珊

加油吧,大家~!!

Share Comments

设计模式之工厂模式

最近在学习设计模式的路上越走越远…

今天主要记录记录一下学习的工厂模式。

工厂模式简历

1、由来    

当我们创建一个对象,而这个对象的创建过程序纷繁复杂,可能导致大量的重复代码的时候,工厂模式出现了。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题。由子类实现这个方法来创建具体类型的对象。也就是说,在我们的实际运用中,工厂就是一个用来创建其他对象的对象。

2、实质

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。

3、现状介绍

工厂模式目前有三个分支,简单工厂模式,工厂方法模式和抽象工厂方法模式,其中,我主要学习的是抽象工厂方法模式,因为该方法是工厂模式中最高层的方法。简单工厂模式,工厂类根据传参不同得到不同的产品。工厂方法模式能够创建这个系列中的各种产品,比如,iphone的各种配件。而抽象工厂模式作为更高级的模式,它可以创建整个苹果公司各个产品流水线,比如,他可以创建生产iphone配件的工厂,可以创建生产ipad配件的工厂等等。那么,抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品等级结构,它能够创建这一个等级结构中的产品族;而抽象工厂模式则需要面对多个产品等级结构,也就是说,抽象工厂方法模式可以工厂方法模式中的工厂。

4、三分支的关系

简单工厂模式通常伴随着对象的具体类型与工厂具体类型的一一对应,客户端代码根据需要选择合适的具体类型工厂使用。当这个选择包含复杂的逻辑时,就可以创建一个单一的工厂类,用以包含这种选择逻辑,根据参数的不同选择实现不同的具体对象。这个工厂类不需要由每个具体产品实现一个自己的具体的工厂类,所以可以将工厂方法设置为静态方法,这就有了工厂方法模式。而抽象工厂方法模式便是封装着这样一组有着共同主题的工厂方法模式中的工厂。

  

三分支的应用场景

1、简单工厂模式

当我们的主程序中需要自己手动初始化一个对象,但是我们并不想关注具体的初始化过程,这个时候简单工厂模式就是您的选择。

2、工厂方法模式

当你觉得传入参数的这种简单的工厂模式让你程序容易出错,并且程序中的if-else让你的程序看起来有些臃肿,这个时候工厂方法模式就是您的选择。

3、抽象工厂方法模式

当你工厂类需要修改时,而在工厂方法模式中需要修改原有的工厂类,这就违背了设计原则中的OCP(开放闭合原则),这个时候,抽象工厂方法模式就是您的选择。

实际应用

说不如做,看的再多不自己亲手做一做来的有效率,这便是我学习工厂模式之后的心得。

首先,模拟场景。创建宝马车,X6和5系的,那么首先的有一个抽象的汽车工厂,CarFactory,由它来生成X6和5系的工厂,一个汽车工厂生产出来的汽车至少得有个壳,发动机,变速箱啥的,所有就有了方法。创建CarFactory如下:

public interface CarFactory {
  public CarFrame createCarFrame();
  public Engine createEngine();
  public Gearbox createGearbox();
 }

一个汽车的基本配件都出来了,那么所有的配件都应该有各自的接口,我们再分别创建Engine,Gearbox和CarFrame,Engine如下(Gearbox、CarFrame类似):

public interface Engine {
    public void createEngine();
 }

然后我们的X6和5系的都应该有这些个接口吧,我们在分别创建他们各自的Engine,Gearbox和CarFrame,X6的Engine:

 public class BwmXSixEngine implements Engine {
     private String emissions;
     public BwmXSixEngine(String emissions){
         this.emissions = emissions;
   }
@Override
public void createEngine() {
     System.out.println("this BWM XDrive engine,and emissions is "+ this.emissions);
     }
}

5系的Engine:

 public class BwmFiveSeriesEngine implements Engine {
private final String emissions;
public BwmFiveSeriesEngine(String emissions){
    this.emissions = emissions;
    }
 @Override
   public void createEngine() {
       System.out.println("this is BWMFiveSeriesEngine it's emissions of "+this.emissions);
}
}

现在,X6和5系就只差装箱,我们就来创建他们的工厂吧,5系:

public class BwmFiveSeriesFactory implements CarFactory{
@Override
 public CarFrame createCarFrame() {
  return new BwmFiveSeriesCarFrame("中大型车", new int[]{5047,1860,1491},"4门5座三厢车");
}
@Override
 public Engine createEngine() {
      return new BwmFiveSeriesEngine("2.0T");
  }
@Override
public Gearbox createGearbox() {
    return new BwmFIveSeriesGearbox("8挡手自一体");
}
}

X6:

 public class BwmXSixFactory implements CarFactory {
    @Override
    public CarFrame createCarFrame() {
        return new BwmXSixCarFrame("中大型SUV",new int[]{4929,1983,1709},"5门5座SUV");
    }
   @Override
    public Engine createEngine() {
        return new BwmXSixEngine("3.0T");
   }
   @Override
     public Gearbox createGearbox() {
       return new BwmXSixGearbox("8挡手自一体");
   }
}

程序写到这里也快结束了,我们自己在写一个汽车超市来生成这些对象就好了,希望你有一次愉快的购物经历,哈哈~

总结

在抽象工厂模式的运用中,需要有一个总的抽象工厂,这个工厂制造可以生产具体东西的工厂类,比如,CarFactory和BwmXSixFactory之间的关系。然后就是接口的运用。接口对于高抽象层级的类有很大作用,站在高处便能规定对继承该接口的类的行为,但并不具体到每一个继承的类的行为。最后,工厂模式成功的隐藏了类的行为,满足迪米特法则,调用者根本不关心怎么实现的,只要一个接口可以调用便行。

自己在设计模式这一块还比较弱,以后还需多多学习。设计模式每一个都不是太难,重要的是各个击破之后,将他们拿捏在手中能够灵活的运用到实例的工作中。所以,重要的还是自己能力的提升,加油~

Share Comments

浅析javascript原型与对象

在我学习与使用javascript三个月中,我一直对javascript的继承关系以及prototype理解不清,导致很多时候为什么这么用说不出个所以然来。截止到本周为止,通过之前的学习以及自己的再学习,自我感觉算是对这一块有了较为清晰的认识,此文将谈谈我对javascript原型与继承的理解,在此之前,我们首先要知道一些基本的知识。

知识铺垫

1、数据类型

JavaScript中的数据类型在曾经我也有提到过,它包括未定义值(undefined),空值(null),布尔值(boolean),数字(number),字符串(string)以及对象(object),而对象中又包含特殊对象数组(array),并且函数也是对象。其中,字符串(string),对象(object)等都是由构造函数来实现的。讲到这里又得说说JavaScript中的函数了。

2、函数

就我所知的语言都是有函数这个概念的,所以就不再细说。在 JavaScript 中函数也是一个对象,那么对象又是通过什么来创建的呢?对象是作为现有示例(即原型)对象的副本而创建的,该名称就来自于这一概念,此原型对象的任何属性和方法都将显示为从原型的构造函数创建的对象的属性和方法。可以说,这些对象从其原型继承了属性和方法。

普通的函数与构造函数在JavaScript中都是通过function来创建,不同的是构造函数需要通过大写来标明。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.say = function () {
console.log('my name is ' + this.name + ",I'm " + this.age)
}
}

Person.say_hello = function () {
console.log("Hello,I'm" + this.sex)
};

Person.prototype.is_alive = function () {
return true
};

var wfsovereign = new Person('wfsovereign', 21, "boy");

wfsovereign.say(); //output my name is wfsovereign,I'm 21
console.log(wfsovereign.is_alive()); //output true
console.log(wfsovereign.say_hello(), "----"); //output undefined

此例中,创建了构造函数Person,接受参数为name,age,拥有静态方法say_hello(),实例方法say()和is_alive(),使用构造函数创建实例对象wfsovereign,能够调用实例方法,调用静态方法时提示未定义。

在构造函数后通过”.”来添加的方法或属性,称之为静态方法或静态属性,这是实例之后的对象不能访问的。因此,我们通过wfsovereign调用say_hello()时才会提示undefined。

那么,我们写在构造函数的prototype上的方法is_alive()为什么实例过后仍然能够被访问呢?这个问题我们先放放,先来看看实例对象与构造函数间的联系,通过控制台,我们输出

console.log(wfsovereign.prototype);
console.log(wfsovereign.__proto__);
console.log(wfsovereign.constructor);

可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
undefined    //wfsovereign.prototype
Person{
is_alive: function(){}
} //wfsovereign.__proto__)
{
constructor: function Person(name, age, sex) {
is_alive: function () {
__proto__: Object
function Person(name, age, sex) { //wfsovereign.constructor
this.name = name;
this.age = age;
this.sex = sex;
this.say = function () {
console.log('my name is ' + this.name + ",I'm " + this.age)
}
}
}
}
}

可以看到,实例对象wfsovereign没有prototype属性,但是有了指向构造函数Person.prototype的proto属性以及指向构造函数的constructor属性,而Person这一构造函数也有指向object的proto属性,说明Person也是通过object创建的一个实例。这个时候我相信聪明如你就能回答上面提出的问题了—我们写在构造函数的prototype上的方法is_alive()为什么实例过后仍然能够被访问呢?

由此,我们得出,创建的每一个函数都有prototype属性,这是一个指针,它指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。也就是说prototype是通过调用构造函数而创建的那个对象实例的原型对象,并且只有函数才有prototype属性,实例的对象没有该属性,即这里用Person创建的实例wfsovereign是没有prototype这一属性的。

当使用构造函数(Person)创建一个实例(wfsovereign)的时候,实例内部将包含一个内部指针(proto)指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间,实例与构造函数之间通过constructor连接。知道了prototype是什么和怎么来的之后,我们再来看JavaScript的原型链就容易多了。

继承与原型链

1、原型链的理解

JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型。在JavaScript中,一共有两种类型的值,原始值和对象值。每个对象都有一个内部属性 prototype ,我们通常称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象也一定有自己的原型。当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会自己去关联的prototype对象那里寻找,如果prototype没有,就会去关联的创造者那里找,直到prototype为undefined为止,Object的prototype就是undefined即所有原型都终止于 Object.prototype,这样就形成了一条线性的链,我们称之为原型链。JavaScript正是通过原型链来调用关联创造者的属性与方法的即继承。

2、使用原型的好处

可以让对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中,通过指针引用的方式来调用。使用构造函数的主要问题就是每个方法都要在每个实例中创建一遍。

  

Share Comments

阶段随想

孵化园三个月的学习时间让我学到了很多,更让我发现了更多感兴趣的东西,促使着我不断向前,不敢止步。三个月朝着一个方向认真学习,足以有些成效,所有,公司对我们进行了一次期中考试,检验我们学习效果的同时,也提醒了我要进行一次自我总结。本篇博客将对此次期中考学到的东西进行整理总结,并对接下来的学习做一些规划。

关于期中考

某天突然接到消息说公司会对我们进行一次期中考试,兴奋的同时也带着一些紧张,兴奋着即将有机会去向往的TW观摩观摩,紧张着自己的实力是否能得到认可或者说自己目前的实力是否能让HR满意。伴随着紧张期待的心情拿到homework之后,心态却不自觉的平静下来,开始一心钻研需求了,毕竟English Ability还有些low。

读懂需求之后,我并没有按照面向对象的思路来分列出可以抽取的对象,而是直接按照需求来写测试并实现,即TDD。这一块是我目前比较模糊的,我到底应不应首先对需求的总体进行分析,进而罗列出能够抽离出来的对象、类,还是直接按照需求,一小步一小步的来进行Test Driven Development。

当我把整个homework先写测试再实现以后发现现在的程序没有面向对象而是面向过程,这个时候我便着手重构的第二阶段,对项目结构进行重构。将字符串转化为对象那一块提取到ItemsTransformation类中,将对item进行一系列操作的方法提取到ItemStudio类中,再将把item对象输出为字符的方法提取为BuildReceiptView类中,由此,我便有了三个类,执行顺序也很明了,ItemsTransformation -> ItemsStudio -> BuildReceiptView。

接着,我再修改了数据源的获取方式,使该项目有一个支持文本文件输入的通道(There must be a way to supply the application with the input data via text file)。最后,便是建立了类似main的printInventory,来将购物清单打印出来。

整个流程目前来看还是挺简单清晰的,不过,经公司老师一审查,不由漏洞百出的感觉…

1、讲述方式

可能由于紧张的缘故,让我讲述我的项目的时候,我陷入了细节坑,花了十分钟左右来讲述各个流程细节的实现,但是效果并不好。在讲述一个项目的时候,应该有高屋建瓴的感觉,从一个较高的层次来对项目进行说明。比如,整个项目的流程,每个模块的输入输出及其功能等等,这样的描述让人易于理解。这种思想,《金字塔原理》中有提及,但是当时就是没用到。由此可见,思维表述的方式我还有待加强。

2、源代码命名方式

我整个项目中使用的都是带下划线的命名方式,经老师说明,在源代码中尽量都使用驼峰命名的方式,测试文件中才使用下划线的命名。此外,类名应为名词,方法名为动词,这些都是最基本的。

3、安全问题

项目中所用到的数据,例如商品基税,应该赋值给变量再由变量去对商品加以赋值,而不应该直接赋值给商品,这样会与普通的数字没有区别,可能会出现安全问题,而且以后也不方便修改。

4、函数职责

函数功能应该单一,这是一直在强调的问题,可是自己还是犯了。经老师一提点,才发现有一个方法确实有些复杂臃肿,而自己看的时候居然没怎么发现。由此,以后对函数功能的检查也应该增强。

5、面向对象

老师说从我的程序来看,面向对象的感觉不强,但是紧张的我当时都忘了请教这个项目如何才能做出面向对象的感觉了。嗯,不过这给以后的修行增加了课题,便是面向对象。

源码的进化

关于字符串的转化,之前的代码是这样的

  function Transfer_input_to_basic_items(some_items_info){
   this.some_items_info = some_items_info
 }
 Transfer_input_to_basic_items.prototype.get_array_string_of_item_info = function () {
    this.some_items_info = this.some_items_info.split('\n');
  };
 Transfer_input_to_basic_items.prototype.get_basic_items = function () {
     this.get_array_string_of_item_info();
   var basic_items = [];
   function build_item_from_input_string(one_info) {
       var item = {};
       var one_item_info = one_info.split(" at ", 2);
       var count_and_name_info = one_item_info[0].split(" ");
       var name_info = _.last(count_and_name_info, count_and_name_info.length - 1);
       var name_from_one_info = _.reduce(name_info, function (memo, str) {
           return memo + ' ' + str;
       }, '');
       item.name = name_from_one_info.trim();
       item.count = parseInt(count_and_name_info[0]);
       item.price = parseFloat(one_item_info[1].trim());
       item.summary = item.count * item.price;
       return item
   }
   _(this.some_items_info).each(function (item_info){
       basic_items.push(build_item_from_input_string(item_info));
   });
   return basic_items
};

可以看到,命名方式均以下划线方式命名,参数名字也不好,还有局部函数,这个局部函数是我自己的问题,把一个简单问题复杂化了,也可以说是我对字符串处理能力不足。下面上回来之后重构的,

 function ItemsTransformation(someItemsInfo) {
   this.someItemsInfo = someItemsInfo
 }
ItemsTransformation.prototype._getArrayStringOfItemInfo = function () {
   this.someItemsInfo = this.someItemsInfo.split('\n');
};

 ItemsTransformation.prototype.getBasicItems = function () {
  this._getArrayStringOfItemInfo();
  var basicItems = [];
  _(this.someItemsInfo).each(function (itemInfo) {
      var item = {};
      item.name = itemInfo.slice(itemInfo.indexOf(' ') + 1, itemInfo.indexOf(' at '));
      item.count = parseInt(itemInfo.slice(0, itemInfo.indexOf(' ')));
        item.price = parseFloat(itemInfo.slice(itemInfo.indexOf(' at ') + 4, itemInfo.length));
      item.summary = item.count * item.price;
      basicItems.push(item);
  });
  return basicItems
};

可以看到,函数、变量命名方式全部改为驼峰了,并且优化了算法,去掉了那个看起来怪怪的局部方法。下面再看看之前功能不单一的类,

 function Items_studio(items) {
    this.items = items;
  }

 Items_studio.prototype._add_basic_sales_tax_rate = function (one_item) {
    one_item.basic_tax_rate = 0.1;
    _(loadAllExemptionBasicItemsCategory).each(function (ExemptionBasicItemsCategory) {
        _(ExemptionBasicItemsCategory.name).each(function (name) {
            if (one_item.name == name) {
               one_item.basic_tax_rate = 0
           }
       })
   });
};    

Items_studio.prototype._add_import_duty = function (one_item) {
   one_item.import_duty_rate = 0;
   if (one_item.name.indexOf('imported ') > -1) {
       one_item.import_duty_rate = 0.05
   }
};

Items_studio.prototype._add_after_tax_summary = function (one_item) {
   var calculator = new Calculator;
   var taxes = calculator.basic_tax_value(one_item) + calculator.import_duty_value(one_item);
   taxes = parseFloat(taxes.toString().substring(0, taxes.toString().indexOf('.') + 3));
   if (parseInt(taxes.toString().substring(3, 4)) < 5 && parseInt(taxes.toString().substring(3, 4)) != 0) {
       taxes = parseFloat(taxes.toString().substring(0, 3) + '5');
       one_item.after_tax_summary = taxes + one_item.summary;
   } else {
       one_item.after_tax_summary = parseFloat((Math.round(taxes * 10) / 10 + one_item.summary).toFixed(2));
   }
};

Items_studio.prototype.get_receipt_items = function () {
   var self = this;
   _(this.items).each(function (one_item) {
       self._add_basic_sales_tax_rate(one_item);
       self._add_import_duty(one_item);
       self._add_after_tax_summary(one_item);
   });
   return this.items
};

 module.exports = Items_studio;

可以看到_add_after_tax_summar()这个方法确实是有些臃肿不美观了,因为这个方法做了得到税金以及对税金按照取舍规则进行取舍,下面上回来之后重构的这部分代码,

ItemsStudio.prototype._addAfterTaxSummary = function (item) {
var calculator = new Calculator;
var taxes = calculator.getBasicTaxValue(item) + calculator.getImportDutyValue(item);
taxes = parseFloat(taxes.toString().substring(0, taxes.toString().indexOf('.') + 3));
item.afterTaxSummary = (this._getValueFromRule(taxes) + item.summary).toFixed(2);
};

ItemsStudio.prototype._getValueFromRule = function (taxes) {
if (parseInt(taxes.toString().substring(3, 4)) < 5 && parseInt(taxes.toString().substring(3, 4)) != 0) {
    taxes = parseFloat(taxes.toString().substring(0, 3) + '5');
    return taxes
} else {
    return Math.round(taxes * 10) / 10
}
};

这样看起来不仅干净了许多,而且可读性也得到了大大的增强。

短期规划

经此一役,发现自己还有很多可提高的部分,

  1. 思维表述方式。准备再啃啃《金字塔原理》,将里面所介绍的方法进行自我练习。

  2. 面向对象。面向对象的思想还很浅薄,这也是目前我的一个瓶颈,攻克此瓶颈的方法便是多读书,多读一些关于介绍面向对象思想的书。

  3. 提高自我要求。对coding中出现的任何臃肿都应立即着手解决。

总的来说,小菜鸟一枚,但我是有理想有目标的小菜鸟,哈哈哈~

Share Comments