繁体   English   中英

OOP语言的函数式编程

[英]Functional programming in OOP language

不可变对象是可以的,但是,是否可以非最终本地引用

换句话说,下一个代码片段可以表示为功能样式?

Employee e = new Employee("Lex", 24, 250);
e = Employee.setName(e, "Vasili");
e = Employee.setAge(e, 12);
e = Employee.setSalary(e, 2500);
Employee.log(e);

PS这里所有Employee方法都是静态的,setter是返回新实例的工厂方法。

由于这个问题被标记为'java',我假设问题是关于Java中的FP实践(即不变性)。

今天Java的优秀做法是使用构建器:

Employee e = Employee.builder()
                     .surname("Lex")
                     .age(24)
                     .name("Vasili")
                     .salary(2500)
                     .build();

或静态构造函数:

Employee e = Employee.of("Vasili", "Lex", 24, 2500);

在这两种情况下,都应将“经典”构造函数声明为私有,以确保无法实例化对象并使其可用于处于不一致状态的客户端。

然后,对象变换器应该返回新对象:

Employee.of("Vasili", "Lex", 24, 2500)  // creates an object
        .updateName("Sergey")           // returns 1st modified copy
        .updateSalary(3500);            // returns 2nd modified copy based on 1st copy

遵循这些实践,对非最终本地引用的需求通常会消失。
一个非常流行的例子是Date和Time API

现在,使用可变局部变量。 没关系,但代码可以缩短,并使用方法链更具表现力。 尝试按原样链接静态方法看起来不会很优雅:

Employee e = setSalary(setAge(setName(new Employee("Lex", 24, 250), "Vasili"), 12), 2500);

作为模拟monad的尝试,可以将对象包装在一个类似monad的容器中,该容器定义一个接受一个函数的bind方法,该函数将接受存储在monad中的对象并返回一些结果,这将再次包装在monad中。 一个简单的例子如下所示:

static class Employee {
    public String name;
    public int age;
    public long salary;
}

static class Monad<T> {
    private final T value;

    private Monad(T value) { this.value = value; }

    public static <T> Monad<T> of(T value) {
        return new Monad<>(value);
    }

    public T getValue() { return value; }

    public Monad<T> bind(UnaryOperator<T> operator){
        return of(operator.apply(value));
    }
}

public static void main(String[] args) {
    Employee value = Monad.of(new Employee())
                          .bind(e -> {e.name = "Lex"; return e; })
                          .bind(e -> {e.age = 24; return e; })
                          .bind(e -> {e.salary = 2500; return e; })
                          .getValue();
}

但是这可以从版本8开始使用核心Java - Stream API可以做到这一点以及更多:

Stream.of(new Employee())
      .map(e -> {e.name = "Lex"; return e; })
      .map(e -> {e.age = 24; return e; })
      .map(e -> {e.salary = 2500; return e; })
      .findFirst()
      .get();

它类似于每个函数返回新项的术语,在许多FP语言(例如Haskell)中,您甚至无法更新值,只需创建新值:

let myBook = beginBook "Haskell"
let myBook' = addChapter (Chapter "Intro" ["Hello","World"]) myBook

所以这里的beginBook函数将返回一本书,然后addChapter将返回另一本修改了一些字段的书。

Haskell一无所知,但我相信你正在努力实现这样的目标:

Employee e = new Employee("Lex")
    .setAge(25)
    .setSalary(2500)
    .setGender(Gender.Male);

这只是以下列方式chaining功能的结果

public Employee setParam(param){
   this.param = param;
   return this;
}

但这些方法不是静态的,它们属于实例。

此外,不需要将实例作为参数传递。


也:

  • this不是必需的关键字; 在上面的例子中,这两个参数具有相同的名称,因此如果没有this ,代码基本上会将param的值重新赋值给自己。 如果这些参数有不同的名称, this是不需要的。 然而,返回this是必要的,因为它代表了对当前实例的引用。

例如:

public Employee setParam(String param) {
    parameter = param; // parameter is a field in class Employee
    return this; // this "this", is still necessary
}
  • final变量

可能会限制你尝试用你的风格实现的目标

final Employee e = Employee.setName(e, "Name"); // invalid, e is unkown

// ----------------

final Employee e;
e = Employee.setName(e, "Name"); // invalid, e may not be initialized

// ----------------

final Employee e;
e = Employee.setName("Name"); // valid

// ----------------

final Employee e = null;
e = Employee.setName(e, "Name"); // invalid. e was already initalized to null

// ----------------

final Employee e = Employee.setName("Name"); // valid
e = Employee.setName("Name2"); // invalid, final variable already initialized

这样的事情会对你有用吗(忽略对OptionalifPresent的错误使用,我们可以用更有意义的东西替换它)?

    Optional.of( new Employee("Lex", 24, 250) )
    .map( e -> Employee.setName(e, "Vasili") )
    .map( e -> Employee.setAge(e, 12) )
    .map( e -> Employee.setSalary(e, 2500) )
    .ifPresent( e -> Employee.log(e) );

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM