Java链式编程学习

Java链式编程

在使用jquery时肯定对它的链式编程惊艳到,慢慢的其它语言这种编程模式也逐渐增多。其本身并不复杂,在调用方法时,方法最后返回对象本身,以达到链式编程的效果。

链式编程比较简单,只要return this即可具有相应的编程模式,但是需要根据业务需求使用不同的方法方式进行实现。

简单的链式编程

既然使用return this可以实现链式模式,那就依次为方案,实现一个简单的例子。

public class Book {
    private String bookId;
    private String title;
    private String cover;

    public String getBookId() {
        return bookId;
    }

    public Book setBookId(String bookId) {
        this.bookId = bookId;
        // 返回当前对象
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Book setTitle(String title) {
        this.title = title;
        // 返回当前对象
        return this;
    }

    public String getCover() {
        return cover;
    }

    public Book setCover(String cover) {
        this.cover = cover;
        // 返回当前对象
        return this;
    }
    
    @Override
    public String toString() {
        return "Book{" +
                "bookId='" + bookId + '\'' +
                ", title='" + title + '\'' +
                ", cover='" + cover + '\'' +
                '}';
    }
}

代码比较简单,就是一个图书的模型,将三个字段的Set方法都返回了对象本身。看一下调用

public static void main(String[] args) {
        Book book = new Book().setBookId("b.001").setTitle("庆余年").setCover("http://localhost/qyn.jpg");
        System.out.println(book);
}
// Book{bookId='b.001', title='庆余年', cover='http://localhost/qyn.jpg'}

编码过程中,完成一个Set方法后按下.就会继续提醒其他方法,实现了链式编程的效果。并且最终结果也符合预期。

静态初始化方法of

目前Spring中有很多方法提供了静态of方法,可以更方便的创建对象,结合return this将更为实用的实现链式编程,算是一种比较不错得扩展措施。在这种模式中,大量的使用了of替代了构造函数。

public class Pageable {
    // 页码,从1开始
    private long page;

    // 页大小
    private int size;

    // 结果命中数量
    private long count;

    private Pageable(long page, int size) {
        this.page = page;
        this.size = size;
    }

    private Pageable(long page, int size, long count) {
        this(page, size);
        this.count = count;
    }

    // PageRequest时使用
    public static Pageable of(long page, int size) {
        return of(page, size, 0);
    }

    // 返回结果时,具有命中数量及返回总页码计算使用
    public static Pageable of(long page, int size, long count) {
        return new Pageable(page, size, count);
    }

    // 分页时,获取偏移量
    public long getOffset() {
        return (page - 1) * size;
    }

    // 返回总页码
    public long getPageCount() {
        return (long) Math.ceil((double) count / size);
    }

    public long getPage() {
        return this.page;
    }

    public int getSize() {
        return this.size;
    }

    public long getCount() {
        return this.count;
    }
}

上面示例中的分页工具类,只实现了三个成员变量页码、页大小、条目数,并且未实现成员变量的Set方法;构造方法全部为私有,实现了静态的of方法来创建分页对象。在该对象中同时提供了获取分页偏移量的getOffset方法,以及计算出总页码的getPageCount方法。

该工具一般有两种使用场景:

  • 数据库查询后,返回数据列表以及分页信息,此时需要页码、页大小、条目数三个信息,使用及实现都比较方便
  • 内存分页时,可以提供一个分页偏移量的计算方法
public static void main(String[] args) {
    Pageable pageable = Pageable.of(10, 10, 123);
    System.out.println(pageable.getOffset());
    System.out.println(pageable.getPageCount());
}
// 90
// 13

如果采用FastJson进行序列化,上述设计方式也能够满足返回JSON的需求,FastJson按照Get方法进行生成属性列表。

public static void main(String[] args) {
    System.out.println(JSON.toJSONString(Pageable.of(10, 10, 123)));
}
// {"count":123,"offset":90,"page":10,"pageCount":13,"size":10}

Builder模式链式编程

Builder模式也是目前较为常用的一种链式编程方法,目前Spring中有大量的依据此模式进行的编码。以最为常见的ResponseEntity(或者Swagger模块也大量采用了此类方法)类来看,其内部定义了Builder接口,并默认实现了一个DefaultBuild内部类,内部方法采用return this实现链式模式,同时在ResponseEntity中提供了类似的Builder方法,如ok、status等静态方法。

从这些类的实现原理看,实现Builder模式也比较简单,一方面创建一个内部Builder类,实现相应信息的创建;同时在目标类中,实现一个静态的builder方法,用于创建Builder对象,并启动链式模式;最后再由内部类提供一个终止方法,该终止方法将完成目标类的创建。

public class ApiInfo {
    private String name;
    private String description;
    private String url;
    private Map<String, String> params;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Map<String, String> getParams() {
        return params;
    }

    public void setParams(Map<String, String> params) {
        this.params = params;
    }

    @Override
    public String toString() {
        return "ApiInfo{" +
                "name='" + name + '\'' +
                ", description='" + description + '\'' +
                ", url='" + url + '\'' +
                ", params=" + params +
                '}';
    }

    public static ApiBuilder builder() {
        return new ApiBuilder();
    }

    // 内部Builder类
    public static class ApiBuilder {
        private String name;
        private String description;
        private String url;
        private Map<String, String> params;
        // name信息设置,链式
        public ApiBuilder name(String name) {
            this.name = name;
            return this;
        }
        // 注释信息设置
        public ApiBuilder description(String description) {
            this.description = description;
            return this;
        }

        public ApiBuilder url(String url) {
            this.url = url;
            return this;
        }
        // 参数信息设置
        public ApiBuilder params(String name, String description) {
            this.params = Optional.ofNullable(params).orElseGet(() -> Maps.newHashMap());
            this.params.put(name, description);
            
            return this;
        }
        // 创建ApiInfo对象
        public ApiInfo build() {
            ApiInfo apiInfo = new ApiInfo();
            apiInfo.setName(this.name);
            apiInfo.setDescription(this.description);
            apiInfo.setUrl(this.url);
            apiInfo.setParams(this.params);

            return apiInfo;
        }
    }
}

上述示例采用Builder模式实现了一个Api接口信息的对象设计,主要是能够设置接口名、地址以及相应的参数。内部设计了一个ApiBuilder,可以实现链式属性设置,并最终提供一个build方法用于终止时创建目标实例。

public static void main(String[] args) {
    ApiInfo apiInfo = ApiInfo.builder().name("获取资源信息")
            .description("根据资源ID获取资源信息")
            .url("/resource/:id")
            .params("id", "资源唯一ID")
            .params("token", "令牌")
            .build();
    System.out.println(apiInfo);
}
// ApiInfo{name='获取资源信息', description='根据资源ID获取资源信息', url='/resource/:id', params={id=资源唯一ID, token=令牌}}

lombok链式编程

链式编程比较简单,一般也就是return、静态of、Builder几种模式,可以直接编码实现,Spring以及一些开源项目Swagger等等都是这么做的。如果我们不是为了做一个通用的开源产品,只是业务性的编码,此时不需要通过大量的编码去实现链式编程,可以采用lombok进行实现,使用也比较简单。

使用lombok时,可以通过maven引入,也可以通过idea安装插件的方式引入。

return链式

仍然使用此前的例子Book类,进行改造,采用lombok注解进行编码。

lombok实现return this链式,只需要在类添加注释@Accessors(chain = true)即可实现

@Getter
@Setter
@ToString
@Accessors(chain = true)
public class Book {
    private String bookId;
    private String title;
    private String cover;

    public static void main(String[] args) {
        Book book = new Book().setBookId("b.001").setTitle("庆余年").setCover("http://localhost/qyn.jpg");
        System.out.println(book);
    }
}

如果在IDE中,查看类接口(ctrl+o),可以发现属性的Set方法,返回类型为Book,和自己编码的方案一致。

public static void main(String[] args) {
        Book book = new Book().setBookId("b.001").setTitle("庆余年").setCover("http://localhost/qyn.jpg");
        System.out.println(book);
}
// Book{bookId='b.001', title='庆余年', cover='http://localhost/qyn.jpg'}

静态初始化方法

对Pageable类进行改造,使用lombok实现of方法。

静态of方法的添加,需要通过lombok的构造函数注解进行添加,RequiredArgsConstructor可以实现必备参数列表的构造,并通过staticname指定静态方法为of;针对上例中的Pageable对象提供了两种静态构造方法,一种是只需要指定页码和页大小即可,还有一种是全部参数的构造函数,可以计算总页码;RequiredArgsConstructor可以实现必备参数的构造;AllArgsConstructor实现全部参数的构造函数,并指定staticNameof,通过lombok的注解组合,实现Pageable与上例自己编码同等效果。

@Getter
@ToString
@Accessors(chain = true)
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(staticName = "of")
public class Pageable {
    /**
     * 页码,从1开始
     */
    @NonNull
    private long page;

    /**
     * 页大小
     */
    @NonNull
    private int size;

    /**
     * 结果命中数量
     */
    private long count;

    /**
     * 分页时,获取偏移量
     *
     * @return
     */
    public long getOffset() {
        return (page - 1) * size;
    }

    /**
     * 返回总页码
     *
     * @return
     */
    public long getPageCount() {
        return (long) Math.ceil((double) count / size);
    }
}

通过组合注释实现了同样的效果,使用也和原有编码效果等同。

public static void main(String[] args) {
    Pageable pageable = Pageable.of(10, 10);
    System.out.println(pageable.getOffset());
    // 全部构造
    System.out.println(JSON.toJSONString(Pageable.of(10, 10, 123)));
}
// 90
// {"count":123,"offset":90,"page":10,"pageCount":13,"size":10}

Builder模式

对ApiInfo进行改造,使用lombok实现builder模式。

lombok实现Builder模式,只要添加@Builder注解即可

@Getter
@Setter
@Builder
@ToString
public class ApiInfo {
    private String name;
    private String description;
    private String url;
    private Map<String, String> params;
}

使用上和之前的例子差不多,不过对于params默认只能是Set的形式。

public static void main(String[] args) {
    ApiInfo apiInfo = ApiInfo.builder().name("获取资源信息")
            .description("根据资源ID获取资源信息")
            .url("/resource/:id")
            .params(new HashMap<String, String>() {
                {
                    put("id", "资源唯一ID");
                    put("token", "令牌");
                }
            })
            .build();
    System.out.println(apiInfo);
}
// ApiInfo(name=获取资源信息, description=根据资源ID获取资源信息, url=/resource/:id, params={id=资源唯一ID, token=令牌})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 文章核心 其实,本不想把标题写的那么恐怖,只是发现很多人干了几年java以后,都自认为是一个不错的java程序员了...
    java菜阅读 1,905评论 6 49
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,707评论 2 9
  • 文章核心 其实,本不想把标题写的那么恐怖,只是发现很多人干了几年java以后,都自认为是一个不错的java程序员了...
    Gallrax阅读 681评论 0 4
  • 图片发自简书App title: 细思极恐-你真的会写java吗?date: 2017-03-31 17:01:1...
    luosv阅读 834评论 0 11
  • 1、链式编程定义 链式编程的原理就是返回一个this对象,就是返回本身,达到链式效果。 我们经常用的 String...
    左右_d488阅读 11,289评论 1 6