概述###

做报表时,有时需要根据不同的业务生成不同的报表。这样,需要能够动态地配置列字段,并根据列字段来输出对应的报表。使用函数接口结合枚举可以比较优雅地实现配置式编程。

问题描述如下:

假设有对象 Student, Teacher ,它们均有属性 Id, Name, Able 。 要求:(1) 打印这些 Student, Teacher 的字段 (Id, Name) 的内容 ; (2) 打印这些 Student, Teacher 的字段 (Name, Able) 的内容。

Java代码示例###

直接上代码。应该能看懂。 需要 Java1.8 才能正常运行。

接口定义####

package zzz.study.function;

/**
* Created by shuqin on 17/3/30.
*/
public interface Person { String getId();
String getName(); String able();
}

对象定义####

类 Student :

package zzz.study.function;

/**
* Created by shuqin on 17/3/30.
*/
public class Student implements Person { private String studentId;
private String name;
private String able; public Student(String studentId, String name, String able) {
this.studentId = studentId;
this.name = name;
this.able = able;
} @Override
public String getId() {
return studentId;
} @Override
public String getName() {
return name;
} @Override
public String able() {
return able;
}
}

类 Teacher :

package zzz.study.function;

/**
* Created by shuqin on 17/3/30.
*/
public class Teacher implements Person { private String teacherId;
private String name;
private String able; public Teacher(String teacherId, String name, String able) {
this.teacherId = teacherId;
this.name = name;
this.able = able;
} @Override
public String getId() {
return teacherId;
} @Override
public String getName() {
return name;
} @Override
public String able() {
return able;
}
}

字段定义配置####

字段定义配置是核心。 这里结合了枚举和函数式接口。这里之所以写成 FieldEnum(fieldName, fieldTitle, fieldValueGetMethod) 的定义方式,是为了便于管理。同样可以采用两个 Map 来实现:Map<fieldName, fieldTitle>, Map<fieldName, fieldValueGetMethod>,这样更适用于 Java1.6 , 不过要把两个 Map 拼起来才是完整的字段定义视图。 Person::getName 是方法引用,(Person p) -> p.getName() 的简写形式。

package zzz.study.function;

import java.util.function.Function;

/**
* Created by shuqin on 17/3/30.
*/
public enum FieldConf { Id("Id", "编号", Person::getId),
Name("Name", "姓名", Person::getName),
Able("Able", "能力", Person::able); private String name;
private String title;
private Function<Person, String> method; FieldConf(String name, String title, Function<Person,String> method) {
this.name = name;
this.title = title;
this.method = method;
} public String getName() {
return name;
} public String getTitle() {
return title;
} public Function<Person, String> getMethod() {
return method;
}
}

字段定义伴生类####

FieldConfAccompany 是 FieldConf 的伴生类, 从 Scala 的伴生对象借鉴而来,体现了 类变量、方法 与 实例变量、方法 分离的设计思想,使得两者各司其责, 都比较简洁。

package zzz.study.function;

import java.util.*;

/**
* Created by shuqin on 17/3/30.
* FieldConf 的伴生对象, 从Scala借鉴而来
*/
public class FieldConfAccompany { private static Map<String, FieldConf> fieldConfMap = new HashMap<String, FieldConf>();
private static List<String> allFields = new ArrayList<>(); static {
for (FieldConf fc: FieldConf.values()) {
fieldConfMap.put(fc.name(), fc);
allFields.add(fc.getName());
}
} public static FieldConf getInstance(String name) {
return fieldConfMap.get(name);
} public static List<String> getAllFields() {
return Collections.unmodifiableList(allFields);
}
}

客户端使用####

这里使用了 java8 Stream api 。 并没有什么特别的,只是针对列表的批量流式处理,具备延迟计算特性。

package zzz.study.function;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; /**
* Created by shuqin on 17/3/30.
*/
public class Report { public static void main(String[] args) {
report(Arrays.asList(new String[] {"Id", "Name"}), getPersons());
report(Arrays.asList(new String[] {"Name", "Able"}), getPersons());
} public static void report(List<String> fields, List<Person> persons) {
String reportTitles = fields.stream().map(
field -> FieldConfAccompany.getInstance(field).getTitle()
).collect(Collectors.joining(",")); List<String> rows = persons.stream().map(
p -> fields.stream().map(
field -> FieldConfAccompany.getInstance(field).getMethod().apply(p)
).collect(Collectors.joining(","))
).collect(Collectors.toList()); System.out.println(reportTitles);
System.out.println(String.join("\n",rows)); } private static List<Person> getPersons() {
Person s1 = new Student("s1", "liming", "Study");
Person s2 = new Student("s2", "xueying", "Piano");
Person t1 = new Teacher("t1", "Mr.Q", "Swim");
Person t2 = new Teacher("t2", "Mrs.L", "Dance");
return Arrays.asList(new Person[] {s1, s2, t1, t2});
} }

输出:

编号,姓名
s1,liming
s2,xueying
t1,Mr.Q
t2,Mrs.L
姓名,能力
liming,Study
xueying,Piano
Mr.Q,Swim
Mrs.L,Dance

Scala代码示例###

业务类####

同样先定义业务类 Person, Student, Teacher 。 可以看到 Scala 的类定义比 Java 类定义的语法形式简洁不少,相当于语言层面实现了 lombok 的功能。

package scalastudy.extend

/**
* Created by shuqin on 17/4/10.
*/
trait Person {
def getId: String
def getName: String
def getAble: String
} class Student(studentId: String, name:String, able:String) extends Person {
override def getId: String = studentId
override def getName: String = name
override def getAble: String = able
} class Teacher(teacherId: String, name:String, able:String) extends Person {
override def getId: String = teacherId
override def getName: String = name
override def getAble: String = able
}

字段配置####

Scala 没有直接支持枚举类型,而是提供了 Enumeration 助手类。 可是这个类也不支持定义方法字段,因此,采用样例对象来模拟枚举功能。这里 apply 实现了静态构造器的功能,通过指定名称获取对应的样例对象。Scala 的 Case 功能非常强大,可以匹配常量、变量、容器结构及元素、类对象、正则表达式等各种对象,并赋值给相应的变量。

package scalastudy.extend

/**
* Created by shuqin on 17/4/10.
* Scala 实现枚举; 由于 Enumeration 不支持枚举含有方法字段,因此采用样例对象模拟实现。
*/
sealed class FieldConf(name:String, title:String, able: (Person)=>String) {
def getTitle = title
def getAble = able
} object FieldConf { def apply(name: String): FieldConf = {
name match {
case "Id" => Id
case "Name" => Name
case "Able" => Able
case _ => Unknown
}
}
} case object Id extends FieldConf("Id", "编号", p => p.getId)
case object Name extends FieldConf("Name", "姓名", p => p.getName)
case object Able extends FieldConf("Able", "能力", p => p.getAble)
case object Unknown extends FieldConf("Unknown", "未知", p => "")

报表输出####

Scala 提供了相当多的助手方法,可以方便地实现常用功能,比如对列表拼接字符串。 在 Java 中就要难受地一次次编写无聊的 new StringBuilder , append, return sb.toString 这种套话, 而在 Scala 只要使用 mkString 即可,类似于 Python 的 join 方法。 Scala 的 lambda 表达式也很简洁,如果只有单变量的话,不必显式写出 p => doFor(p) 的形式, 而是直接可写成 doFor(_) 。

package scalastudy.extend

/**
* Created by shuqin on 17/4/10.
*/
object ExtendedReport extends App {
launch() def launch(): Unit = {
report(List("Id", "Name"), getPersons())
report(List("Name", "Able"), getPersons())
} def report(fields:List[String], persons:List[Person]):Unit = {
val titles = fields.map(FieldConf(_).getTitle).mkString(",")
println(titles)
val rows = persons.map(
p => fields.map(FieldConf(_).getAble.apply(p)).mkString(",")
).mkString("\n")
println(rows)
} def getPersons():List[Person] = {
List(new Student("s1", "liming", "Study"), new Student("s2", "xueying", "Piano"),
new Teacher("t1", "Mr.Q", "Swim"), new Teacher("t2", "Mrs.L", "Dance"))
} }

使用函数接口和枚举实现配置式编程(Java与Scala实现)的更多相关文章

  1. PHP 面向对象编程和设计模式 (1/5) - 抽象类、对象接口、instanceof 和契约式编程

    PHP高级程序设计 学习笔记 2014.06.09 什么是面向对象编程 面向对象编程(Object Oriented Programming,OOP)是一种计算机编程架构.OOP 的一条基本原则是计算 ...

  2. 函数响应式编程(FRP)框架--ReactiveCocoa

    由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...

  3. 07-Spring5 WebFlux响应式编程

    SpringWebFlux介绍 简介 SpringWebFlux是Spring5添加的新模块,用于Web开发,功能和SpringMvc类似的,WebFlux使用当前一种比较流行的响应式编程框架 使用传 ...

  4. JDK8新特性(二) 流式编程Stream

    流式编程是1.8中的新特性,基于常用的四种函数式接口以及Lambda表达式对集合类数据进行类似流水线一般的操作 流式编程分为大概三个步骤:获取流 → 操作流 → 返回操作结果 流的获取方式 这里先了解 ...

  5. java8流式编程(一)

    传送门 <JAVA8开发指南>为什么你需要关注 JAVA8 <Java8开发指南>翻译邀请 Java8初体验(一)lambda表达式语法 Java8初体验(二)Stream语法 ...

  6. IO端口和IO内存的区别及分别使用的函数接口

    每个外设都是通过读写其寄存器来控制的.外设寄存器也称为I/O端口,通常包括:控制寄存器.状态寄存器和数据寄存器三大类.根据访问外设寄存器的不同方式,可以把CPU分成两大类.一类CPU(如M68K,Po ...

  7. Advapi32.dll 函数接口说明

    Advapi32.dll 函数接口说明             函数原型                               说明    AbortSystemShutDown         ...

  8. .NET Core CSharp初级篇 1-5 接口、枚举、抽象

    .NET Core CSharp初级篇 1-5 本节内容类的接口.枚举.抽象 简介 问题 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观? 你是否发现,无论何种电脑,它的USB口的设计 ...

  9. 【linux开发】IO端口和IO内存的区别及分别使用的函数接口

    IO端口和IO内存的区别及分别使用的函数接口 每个外设都是通过读写其寄存器来控制的.外设寄存器也称为I/O端口,通常包括:控制寄存器.状态寄存器和数据寄存器三大类.根据访问外设寄存器的不同方式,可以把 ...

随机推荐

  1. linux批量替换文本字符串

    (一)通过vi编辑器来替换.vi/vim 中可以使用 :s 命令来替换字符串.:s/well/good/ 替换当前行第一个 well 为 good:s/well/good/g 替换当前行所有 well ...

  2. 筛选BETWEEN '2018-1-1 00:00:00' AND '2018-5-18 00:00:00'每日`status`='1'的记录总条数

    最近做了一个小任务,要求是:使用MySQL #筛选BETWEEN '2018-1-1 00:00:00' AND '2018-5-18 00:00:00'每日`status`='1'的记录总条数 SE ...

  3. aws的安全组

    aws有安全组来控制进入和去除的规则. 入站:就是外网访问你出站:就是你访问外网用户可以创建入站和出站规则,从而阻挡或者允许特定程序或者端口进行连接;用户可以将规则应用于一组程序.端口或者服务,也可以 ...

  4. 误删除innodb ibdata数据文件 文件句柄 文件描述符 proc fd

    误删除innodb ibdata数据文件  文件句柄  文件描述符  proc  fd http://www.cnblogs.com/gomysql/p/3702216.html 提示:如果不小心通过 ...

  5. 并发编程---开启进程方式---查看进程pid

    1.开启进程的两种方式 方式一: from multiprocessing import Process import time def task(name): print('%s is runnin ...

  6. 编辑文件 vi,vim的基本操作

    vim   文件名字进入文件后   按i  进行编辑编辑确认后   按 Esc  停止编辑然后              按:   输入  wq     (是root权限时才行:若是强制修改 需要  ...

  7. SVM高斯核为何会将特征映射到无穷维?【转载】

    转自:https://www.zhihu.com/question/35602879 1.问题: SVM中,对于线性不可分的情况下,我们利用升维,把低维度映射到到维度让数据变得“更可能线性可分”,为了 ...

  8. datetime函数和random.seed()函数的应用

    一,datetime 在python中datetime是一个库是一个模块也是一个函数,作用很多,这里面只对其做简单的最常用的讲解. 首先返回系统时间 import datetime nowTime=d ...

  9. gedit 没有preference项,使preference回归,并用命令行设置行号,解决centos7下中文乱码,text wrapping等问题

    1. 最简单的,使preference选项回来: gsettings set org.gnome.settings-daemon.plugins.xsettings overrides '@a{sv} ...

  10. java基础(二) -对象和类

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为.例如,一条狗是一 ...