• Java 8 函数式编程与面向对象式编程


    综述

    下面以一个常见的需求为例,分析Java 8的函数式编程与常规的面向对象式编程的不同之处。函数式编程和面向对象式编程最根本的不同之处在于,在面向对象的世界,函数功能不能独立于数据而存在,一个函数功能必须存在于一个包含数据的对象中,服务于特定的数据。也就是说,在面向对象时,对象是编程的最小单元,而一个对象将数据和作用于该数据的函数功能打包成一个整体,数据和函数是不可分割的一部分。此时函数只能为该数据服务,而该数据一般也只能使用定义于其上的函数。而这种函数与数据的结合其实是加深了数据和功能的耦合性。对于编程来说,紧耦合意味着不灵活和低泛化能力。

    面向对象的编程中的诸多设计原则如面向接口编程原则,扩展大于继承的原则等都是为了降低对象间的耦合程度而努力。但面向对象编程是有原罪的,它从出生的那一刻起就将数据和函数绑定到一起。这一点决定了面向对象编程不可能做到彻底的解耦。而函数式编程的目的是将解偶进行到底。他要彻底的解耦。他要做的就是将数据和函数分开,函数是函数,数据是数据,没必要非得在一起。在函数式编程的世界,函数和数据是对等的位置,都作为最小的单元而出现。我的数据可以选择A函数来提供服务,也可以选择B函数来提供服务。我的函数不仅可以给C数据提供服务,也可以给D数据提供服务。我可以按我的需求来随意组合数据和函数,打破在面向对象式编程的世界中数据与函数绑定规则,拥抱更自由的世界。

    在java上谈论函数式编程对有些人来说是一件可笑的事情,因为java从本质上说面向对象的,你不管如何给一只猴子化妆,他也变不成人。但是对有些不怎么挑剔的观众来说,只要化妆的技术足够高超,就可以把猴子当作人来看。他们(包括我)认为,通过对java现有技术的组合,可以实现对函数式编程的一些我们喜欢的特性的模拟,享受函数式编程的快乐和便利。基于此,java8推出Function包,可以一定程度上让我们感受函数式编程的快乐。关于Function包的使用,可以参看Java 8 Consumer、Supplier、Predicate、Function

    言归正传,进入今天的主题。我们通过一个常见的需求,来在讨论函数式编程和面向对象式编程的思想下,分别会有什么样的应对方法。

    需求案例

    传入一个Long型的id List, 将其转换成加上特定前缀后缀的id key。如id为1对应的id key为 Number_01_cache_key

    面向对象编程

    面向对象编程,就需要我们把数据(id List)和对数据的操作(convert函数的逻辑)放到同一个对象中去, 因此像下面这样的操作是典型的面向对象的编程过程:

    DetailReader_Object_way.java

    package ObejectProgramming;
    
    import java.util.ArrayList;
    import java.util.List;
    /**
     * @author longxingjian
     * Created on 2020-01-14
     */
    public class DetailReader_Object_way {
        private List<Long> ids;
    
        public DetailReader_Object_way(List<Long> ids){
            this.ids = ids;
        }
    
        public List<String> convert(){
            String format = "Number_%s_CACHE_KEY";
            List<String> convertedCacheKeys = new ArrayList<>();
            for(Long id:ids)
                convertedCacheKeys.add(String.format(format,id));
            return convertedCacheKeys;
        }
    }
    

    如上将对id的转换逻辑写到convert操作中,这里面的转换逻辑只为本类的ids数据服务,且本类的ids数据只能使用convert操作中定义的转化逻辑。一旦我们的转换逻辑发生了变更,我们也必须修改业务代码。
    下面是测试函数:

    ObjectProgrammingTest.java

    package ObejectProgramming;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.junit.Test;
    
    /**
     * @author longxingjian
     * Created on 2020-01-14
     */
    public class ObjectProgrammingTest {
        @Test
        public void objectProgrammingTest(){
            List<Long> ids =  new ArrayList<>();
            ids.add(1L);
            ids.add(2L);
    
            DetailReader_Object_way detailReader = new DetailReader_Object_way(ids);
            List<String> cacheKeys = detailReader.convert();
            for (String s : cacheKeys) {
                System.out.println(s);
            }
        }
    }
    

    结果:

    函数式编程

    函数式编程的目标是解藕数据和函数服务。在java中对象是提供服务的最小单元,因此一个折衷的办法是,我们定义某一种服务,它只提供类似函数的单一服务,并且以对象传递的形式发送给另一个对象使用,好像我们把这个函数服务单独发送过去了一样,至于接收者怎么使用,那是他们的事情了。
    对于函数式的服务, 在java中由于语言的要求必须以一个对象的形式提供,且我们要求它提供单一的服务,只做一件事,这个功能由java8的Funciton包为我们实现。Function对象的公开方法只有一个apply方法,这个对象作为这种服务的容器来使用。话不多说,来看代码:

    DetailReader_Functional_Way.java

    package FunctionalProgramming;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * @author longxingjian <longxingjian@kuaishou.com>
     * Created on 2020-01-14
     */
    public class DetailReader_Functional_Way {
        private Function<Long, String> cacheKeyFormatter;
        private List<Long> ids;
    
        public DetailReader_Functional_Way(Function<Long, String> function, List<Long> ids) {
            this.cacheKeyFormatter = function;
            this.ids = ids;
        }
    
        public List<String> convert() {
            List<String> convertedCacheKeys = ids.stream().map(cacheKeyFormatter).collect(Collectors.toList());
            return convertedCacheKeys;
        }
    }
    

    这个对象虽然也有一个convert方法,但是我们看到这个方法没有任何的转换逻辑,它的转换逻辑是通过调用cacheKeyFormatter来实现的,而cacheKeyFormatter正是与数据ids一起定义在开头的一个成员,他是一个Function对象,从泛型中可以看出这个函数式对象提供的函数功能是输入Long型输出String型。
    里看看这个对象是怎么传入的:

    CacheKeyTest.java

    package FunctionalProgramming;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.junit.Test;
    
    /**
     * @author longxingjian 
     * Created on 2020-01-14
     */
    public class CacheKeyTest {
        @Test
        public void cacheKeyTest() {
            List<Long> ids = new ArrayList<>();
            ids.add(1L);
            ids.add(2L);
    
            DetailReader_Functional_Way detailReader = new DetailReader_Functional_Way(CacheKeyProvider::getCacheKey, ids);
            List<String> convertedKeys = detailReader.convert();
            for (String key : convertedKeys) {
                System.out.println(key);
            }
        }
    }
    

    注意看DetailReader_Functional_Way detailReader = new DetailReader_Functional_Way(CacheKeyProvider::getCacheKey, ids);
    这里我们通过函数指针为Function对象传入了CacheKeyProvider 的成员函数getCacheKey
    来看看CacheKeyProvider对象是如何定义的:

    CacheKeyProvider.java

    package FunctionalProgramming;
    
    /**
     * @author longxingjian 
     * Created on 2020-01-14
     */
    public class CacheKeyProvider {
        final static String CACHE_KEY = "Number_%S_CACHE_KEY";
    
        public static String getCacheKey(Long id) {
            return String.format(CACHE_KEY, id);
        }
    }
    

    运行CacheKeyTest.java

    梳理上述过程,我们发现java函数式编程的结果是通过提供一种特殊的对象--只提供一种服务的对象来实现对函数式编程的模拟。这种方式虽然也是在对象层面的解耦,但也实实在在的将数据与操作分离开来。在上面的实现中,我们可以通过在CacheKeyTest.java传入不同的函数指针而使用不同的函数逻辑,而不需要去修改DetailReader_Functional_Way.java中的业务代码。在DetailReader_Functional_Way.java中,处理逻辑和被处理的数据是放在对等的位置的。

  • 相关阅读:
    文件参数Python读取wav格式文件
    电子工程术语和定义列表,按字母顺序排列
    MAC地址加减1算法
    uboot通过kernel command line 动态分区 CONFIG_MTD_CMDLINE_PARTS
    c调用shell脚本
    busybox提示can't access tty.job control turned off
    cut命令如何截取以空格隔开的字段
    DS28E01100
    busybox 中的ntpd使用
    Debugfs
  • 原文地址:https://www.cnblogs.com/greatLong/p/12191457.html
Copyright © 2020-2023  润新知