使用函數接口和枚舉實現配置式編程(Java與Scala實現)


概述###

做報表時,有時需要根據不同的業務生成不同的報表。這樣,需要能夠動態地配置列字段,並根據列字段來輸出對應的報表。使用函數接口結合枚舉可以比較優雅地實現配置式編程。

問題描述如下:

假設有對象 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"))
  }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM