Java与模式之重逢 - xiaoR's Blog
Java与模式之重逢
在很久以前,人们对世界的抽象还停留在比较原始的阶段。还流行过一句话,程序=数据结构+算法。随着时间的推移,人们认识到数据和行为的统一性,用类来封装数据,隐藏数据,并且对行为建模。这种更高级的ADT应该是OO的最基本形态.用类继承的方式来渐进式地描述现实生活中的概念.对概念之间的差别用子类覆盖父类某一个方法的途径来表达.这就是所谓的面相对象三大特征"封装,继承,多态".当这个概念被提出时,人们就像回到初恋(自行脑补食神中的沙滩场景),世界一下子就明亮了起来。然而现实还是很骨感的,多重继承深深的打击了人们(初恋往往都是苦涩的)。于是乎站在神坛的4人帮就站出来说:你们这般low X,你们必选要按照一定的“模式”来和异性相处才能更好的向往未来。作为工业界的新生Java君当然会响应这个号召,而去更近一步明确确认了subType(Interface)和subClass之间的职责。这也是为啥模式起于C++却在Java这发扬广发(自己脑补的,带主观排定).那时候你可能会有
或者
或者这个?
到了近代,“大数据”的概念被炒热,某哥发表了“三大论文”,lambad被重新提起。Java君也顺应时代加上了lambda的新衣,今天就带着各位看官走马观花下披上新衣的java与模式的重复。
单列模式
public enum Singleton { INSTANCE; }
在Lambda这边,比如scala,单列是其内置的一种语法元素用object来表示实现一切皆对象的大一统。
object Singeton{}
策略模式
先简单回顾下策略模式的基本要术:
Strategy: 算法的公共接口,于Context交互
Context: 上下文用于Strategy算法的选择
ConcreteStrategy: 算法的具体实现
简单的使用一个根据运行环境来完成支付金额的例子,生产环境为全额,测试环境为0.01
public interface PayStrategy { BigDecimal calculateMoney(BigDecimal money); } /**具体算法实现 **/ public class DebugPayStrategy implements PayStrategy{ public BigDecimal calculateMoney(BigDecimal money){ return new BigDecimal("0.01"); } } public class ProPayStrategy implements PayStrategy{ public BigDecimal calculateMoney(BigDecimal money{ return money; } } /**context 直接使用Main方法了**/ public static void main(String...args){ List<PayStrategy> payStrategyList = Arrays.asList(new DebugPayStrategy(),new ProPayStrategy()); for(PayStrategy strategy : payStrategyList){ System.out.println(strategy.calculateMoney(new BigDecimal("1000"))); } }
使用标准的策略模式需要的代码还是有点小多(定义每个特定的算法实现),现在使用lambda来对其进行改造,看代码:
public static void main(String...args){ List<PayStrategy> payStrategyList = Arrays.asList( (money) -> new BigDecimal("0.01"), (money) -> money ); payStrategyList.foreach(strategy -> { System.out.println(strategy.calculateMoney(new BigDecimal("1000"))); }); } /**如果需要对策略进行选择可以通过Predicate来判定**/ Predicate<PayStrategy> choose = (strategy) -> Objects.equals(strategy.evn,"debug") /**当然也可以用模式匹配去实现(日志策略)**/ public static String addPrefix(String log) { return Matching .when().isMatch(allOf(not(containsString("Exception")),not(containsString("weird")))) .thenApply(message -> "INFO " + message) .when().isMatch(containsString("weird")) .thenApply(message -> "WARN " + message) .when().isMatch(containsString("Exception")) .thenApply(message -> "ERROR " + message) .match(log).get(); }
装饰模式
同样简单回顾下装饰模式的基本要术:
/** * 为了简单,这里直接使用Java8的Function接口的andThen方法来模拟 * 还款金额 = 本金+逾期费+红包减免+服务费 **/ Function<Double, Double> gift = (current) -> current * 0.2; Function<Double, Double> overDue = (current) -> current * 1.2; Function<Double, Double> serviceFree = (current) -> current + 10; List<Function<Double, Double>> decorators = //添加装饰构件对象 new ArrayList<Function<Double, Double>>(){{ add(gift); add(overDue); add(serviceFree); }}; double amount = decorators.stream() .reduce(Function::andThen) /**模拟装饰的过程**/ .orElse(Function.identity()/**base的过程,没有修饰组件的情况**/ .apply(10.0)); /**Function中的两个关键方法andThen和compose可以将多个Function进行串联,最主要的区别在与调用的顺序不同,这里是加法就无所谓了看代码**/ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** * 如果用面向对象的方法去写,可能需要4个装饰类,一个接口....一个...个... * 而去还要这么用 new Gift(new OverDue(new ServiceFree(new .....)))好吧。。。如果你喜欢 *人生苦短,我用lambda **/
借贷模式
借贷模式主要用于一些资源的管理,比如文件,Lock之类的
Properties prop = new Properties(); try (FileInputStream fis = new FileInputStream("config.properties")) { prop.load(fis); } //auto closed
public static void use(String fileName,Consumer<InputStream> block) throws Exception { try (FileInputStream fis = new FileInputStream(fileName)) { block.accept(fis); } } /**使用**/ Properties prop = new Properties(); use("config.properties",input->{ try{ prop.load(input); }catch(IOException e){ //handler exception here } });
改写之后就不需要去关心资源的关闭,但是异常依然带来了好多噪音。接下来我们来处理异常的部分.定一个可以抛出异常的函数式接口
@FunctionalInterface public interface ThrowableConsumer<T,E extends Throwable> { void accept(T t) throws E; } public static void use(String fileName,ThrowableConsumer<InputStream,IOException> block) throws IOException { try (FileInputStream fis = new FileInputStream(fileName)) { block.accept(fis); } } /**这么用**/ Properties prop = new Properties(); use("config.properties",prop::load);//世界是不是一下子就干净了许多
对于Lock就留给各位看官自己去实现了
递归
讲Lambda不得不提的就是递归了,某些函数式语言本身是没有循环结构,而是通过递归的方式来完成对应的操作。比较常见的两种实用方式是尾递归和备忘录(缓存子问题结果).
int factorialTailRecursion(int n, int acc) { if (n == 0) return acc; return factorialTailRecursion(n - 1, acc * n); }
@FunctionalInterface interface StreamRecursively<T> extends Supplier<T> { default boolean complete(){return true;}//本次计算是否已经完成 @Override T get(); default T result(){return get();} /**下一次递归的调用**/ default StreamRecursively<T> next(){ return this; } /**提供简单方法直接完成一个结果**/ static <T> StreamRecursively<T> done(T result){ return () -> result;} /**通过stream吧递归调用串列(Stream本事是一个无穷的列表,即不会产生栈帧), 再使用终止方法 **/ /**filter和findFirst得到结果,当且仅当最后一个调用返回true的时候调用连结束 **/ static <T> StreamRecursively<T> stream(StreamRecursively<StreamRecursively<T>> tasks){ return new StreamRecursively<T>() { @Override public boolean complete(){return false;} @Override public StreamRecursively<T> next(){return tasks.get();} @Override public T get(){return continual(this);} T continual(StreamRecursively<T> call){ /**Java8 Stream模拟递归过程**/ return Stream.iterate(call,StreamRecursively::next) .filter(StreamRecursively::complete) .findFirst() .get() .result(); } }; } } /**使用:为了体现价值,这里使用BigInteger **/ StreamRecursively<BigInteger> factorial(BigInteger acc,BigInteger init){ return BigInteger.ZERO.equals(acc) ? StreamRecursively.done(acc) : StreamRecursively.steam(() -> factorial(acc.subtract(BigInteger.ONE),init.multiply(acc))); } factorial(new BigInteger("20000"),BigInteger.ONE).result();
这里还是有点小噪音(init参数不是必须的),java本事没有curry这个功能只好再封装一个函数把init隐藏掉(这里就不写了啊)。回过头来看为啥经典例子中会StackOverflowError吗?为什么?
DSL
当语言的类型系统和语法(函数式语言)足够强大的时候,使用lambda(高阶函数,函数字面量)就很容易的构建出DSL,比如发邮件,在Java方可能这么去用
@Getter @ToString @Builder @AllArgsConstructor public class Email { private final String from; private final String to; private final String subject; private final String body; public void send(){ //send email } } /**使用**/ Email.builder() .from("xxxx@gmail.com") .to("xxx@qq.com") .subject("hello world") .body("content!!!!") .send();
使用Lambda(Java不支持函数字面量),这里使用kotlin来演示:
class EmailSpec { fun from(from: String) = println("From: $from") fun to(to: String) = println("To: $to") fun subject(subject: String) = println("Subject: $subject") fun body(init: BodySpec.() -> Unit): BodySpec { val body = BodySpec() body.init() return body } } class BodySpec { fun p(p: String) = println("P: $p") } fun email(init: EmailSpec.() -> Unit): EmailSpec { val email = EmailSpec() email.init() return email } /**使用**/ fun main(args:Array[String]){ email { from ("xxx@gmail.com") to ("xxx@qq.com") subject ("hello world") body { p ("content!!!") p ("content!!!") } } }
下边是一个爬虫的例子(猜猜这事啥语言)
def crawl = { navigateTo("http://www.google.com") { in(form having id("tsf")) { in(textField having id("lst-ib")) { typeIn("bplawler") } in(submit having name("btnK")) { click ==>... } } } onCurrentPage { result = from(div having id("resultStats")) getTextContent val url = from( anchor having xPath("//div[@id='field_timetable_file-wrapper']/a") ).getAttributes.getNamedItem("href").getTextContent download(url).writeTo(output) } }
不是彩蛋的彩蛋
在日常开发中我们时常需要把一些第三方的参数转化为javaBean,在参数没有值的时候还需要给出默认值,如果在保证类型安全的前提下来完成这个功能呢?不知道各位看官是否想过Java中的Annotation+动态代理,来看看怎么用吧.
public final class AnnotationPropertyConverter<T>{ private final Class<T> target; private AnnotationPropertyConverter(Class<T> target){this.target = target;} public static <T> AnnotationPropertyConverter<T> to(Class<T> target){ return new AnnotationPropertyConverter<>(target); } @SuppressWarnings("unchecked") public T from(Map<String,String> source) { return (T) Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target}, (proxy, method, args)->{ String value = source.get(method.getName()); if(value == null){ Object defaultValue = method.getDefaultValue(); if(defaultValue == null){ throws new IllegalArgumentException("no default value define"); } return defaultValue; } //apache BeanUtils return converter.convert(value,method.getReturnType()); }); } } //用法 Map<String,String> source = new HashMap<String,String>(){{ put("orderId","123778006102384AF"); put("requestId","787619"); }}; @interface YOrder{ String orderId default ""; int requestId default 0; /**枚举也是可以的,但要注册一个枚举的转换器**/ } YOrder order = AnnotationPropertyConverter.to(YOrder.class).from(source); order.orderId(); order.requestId();
Java8之后interface具有了默认方法,用默认方法也可以做到同样的效果,看代码
public final class Java8DefaultMethodPropertyConverter<T>{ private final Class<T> target; private Java8DefaultMethodPropertyConverter(Class<T> target){ this.target = target; } public static <T> Java8DefaultMethodPropertyConverter<T> to(Class<T> target){ return new Java8DefaultMethodPropertyConverter<>(target); } @SuppressWarnings("unchecked") public T from(Map<String,String> source) { return (T) Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target}, (proxy, method, args)->{ String value = source.get(method.getName()); if(value == null){ if(method.isDefault()){ return MethodHandles.lookup() .in(method.getDeclaringClass()) .unreflectSpecial(method, method.getDeclaringClass()) .bindTo(proxy) .invokeWithArguments(args); }else{ throws new IllegalArgumentException("no default value define"); } } //apache BeanUtils return converter.convert(value,method.getReturnType()); }); } } //用法是一致的
写在最后
通过上面的例子,不难发现函数式和面向对象最大的区别(抛开抽象的角度和其他一些因素,单从代码表达上)就是函数式更多的是使用表达式(Expression),面向对象更多的是使用语句(Statement).Expression
2021年4月03日 15:59 howdy, your websites are really good. I appreciate your work. 123movie
2021年4月05日 18:34
I am continually amazed by the amount of information available on this subject. What you presented was well researched and well worded in order to get your stand on this across to all your readers. 123movie
2021年4月14日 16:54
I am happy to find this post Very useful for me, as it contains lot of information. I Always prefer to read The Quality and glad I found this thing in you post. Thanks online casino
2022年8月02日 16:25
This is a platform which brings the scholarship for students that are offered under central government, state government and thus government bodies, and National Scholarship Portal (NSP) has a list of 50 scholarships which totals to some hundreds of Crores, and it is calculated that a total of Rs 2,400 crores scholarship has been provided under NSP to over 110 lakhs applicants. NSP login National Scholarship Portal (NSP) was introduced under the National e-governance Plan to offer Smart Mission Oriented Accountable Responsive and Transparent platform to provide scholarship for multiple series, hence the beneficiary account added in the portal will be secure and the amount of scholarship will be deposited to the same, and at present there are limited states who are aligned to NSP and their scholarship can be seen in its application.
2022年9月06日 18:13
Bihar Board Model Paper 2023 Class 4 Pdf Download with Answers for English Medium, Hindi Medium, Urdu Medium & Students for Small Answers, Long Answer, Very Long Answer Questions, and Essay Type Questions to Term1 & Term2 Exams at official website. Bihar Board Question Paper Class 4 New Exam Scheme or Question Pattern for Sammittive Assignment Exams (SA1 & SA2): Very Long Answer (VLA), Long Answer (LA), Small Answer (SA), Very Small Answer (VSA), Single Answer, Multiple Choice and etc.
2024年1月12日 01:20
Board Model Papers 2024 Download with Suggestions for 10th Class Textbooks 2024 Pdf Download and SSLC New Syllabus Sample Question Paper 2024 and different types of model papers boardmodelpaper.com and question papers for following the website and Arts, Science, Commerce Stream Subject Wise Solved Question Bank for Hindi & English Medium Students with Exam Pattern & Blueprint and subject Wise with 11th & 12th Question Bank 2024 for General & Vocational Course Languages & Subjects Important Question for the above link.
2024年6月07日 02:22
Looking to buy verified Stripe account for personal and business use? Get the best deals on verified Stripe accounts with all necessary documentation.
2024年6月07日 02:23
Are you afraid to buy our Verified Cash App Accounts service will be Dropped? Don’t Worry, We are not like the rest of the fake PVA Accounts providers. We provide 100% USA real person documents full verified BTC eanble cash app account. We’re working with the largest team and we are able to deliver your purchased cash app account within 30 minutes after completing your order. So, Buy our Service and enjoy.
https://pvasmm.com/product/buy-verified-cash-app-account/
2024年6月07日 02:24
您是否正在寻找最优质的真实、活跃、新旧 Gmail 帐户?您是否想购买美国和欧洲国家/地区通过真实电话号码验证的旧 Gmail 帐户?此外,您是否想以低价获得旧 PVA Gmail 帐户?
那么,您现在来到了购买旧 Gmail 帐户的最佳地点。我们可以为您提供任何国家/地区通过真实电话号码验证的活跃和旧 Gmail 帐户。此外,如果任何 Gmail 帐户在此期间被禁用,我们还提供 180 天无限制免费更换服务。