関数プログラミングはデザインモードを忘れさせます


本文は「Java 8実戦」の読書ノートで、読むのに約5分かかります.
ちょっとタイトル党ですが、これは確かに私が最近Lambda式を使った感じです.設計モデルは過去のいくつかの良い経験と方法の総括であるが、良い言語特性は開発者にこれらの設計モデルを考慮させないことができる.オブジェクト向けの一般的な設計モデルには、戦略モデル、テンプレートメソッド、オブザーバーモデル、責任チェーンモデル、ファクトリモデルがあります.Lambda式(関数プログラミング思考)を使用すると、オブジェクト向けの開発における固定コードを回避できます.次に、ポリシー・モードと職責チェーン・モードの2つのケースを選択して分析します.
ケース1:ポリシーモード
問題を解決するために異なる解法がある場合、お客様はこれらの解法の詳細を感知することを望んでいません.この場合、ポリシー・モードを使用するのに適しています.ポリシー・モードには、次の3つのセクションがあります.
  • 問題を解決するアルゴリズム(上図のStrategy);
  • クラスアルゴリズムの1つまたは複数の具体的な実装(上図中のConcreteStrategyA、ConcreteStrategyB、およびConcreteStrategyC)
  • .
  • 1または複数のクライアント使用シーン(上図のClientContext)
  • オブジェクト向けの考え方
    まず、ソート・ポリシーを表すポリシー・インタフェースを定義します.
    public interface ValidationStrategy {
        boolean execute(String s);
    }
    次に、特定の実装クラス(すなわち、異なるソートアルゴリズム)を定義します.
    public class IsAllLowerCase implements ValidationStrategy {
        @Override
        public boolean execute(String s) {
            return s.matches("[a-z]+");
        }
    }
    
    public class IsNumberic implements ValidationStrategy {
        @Override
        public boolean execute(String s) {
            return s.matches("\\d+");
        }
    }
    
    最後に、次の図に示すように、お客様が使用するシーンを定義します.Validatorは、お客様にサービスを提供するために使用されるコンテキスト環境で、各Valiatorオブジェクトには特定のStrategyオブジェクトがカプセル化されています.実際の作業では、特定のStrategyオブジェクトを交換することで、お客様のサービスのアップグレードを行うことができます.また、お客様にアップグレードする必要はありません.
    public class Validator {
    
        private final ValidationStrategy strategy;
    
        public Validator(ValidationStrategy strategy) {
            this.strategy = strategy;
        }
    
        /**
         *       
         */
        public boolean validate(String s) {
            return strategy.execute(s);
        }
    }
    
    public class ClientTestDrive {
    
        public static void main(String[] args) {
            Validator numbericValidator = new Validator(new IsNumberic());
            boolean res1 = numbericValidator.validate("7780");
            System.out.println(res1);
    
            Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
            boolean res2 = lowerCaseValidator.validate("aaaddd");
            System.out.println(res2);
        }
    }
    関数式プログラミングの考え方
    Lambda式を使用して考えると、ValidationStrategyは関数インタフェース(Predicateと同じ関数記述もある)であることがわかります.では、上記の実装クラスを定義する必要はありません.Lambda式の内部にこれらのクラスが一定のパッケージ化されているため、直接次のコードで置き換えることができます.
    public class ClientTestDrive {
    
        public static void main(String[] args) {
            Validator numbericValidator = new Validator((String s) -> s.matches("\\d+"));
            boolean res1 = numbericValidator.validate("7789");
            System.out.println(res1);
    
            Validator lowerCaseValidator = new Validator((String s) -> s.matches("[a-z]+"));
            boolean res2 = lowerCaseValidator.validate("aaaddd");
            System.out.println(res2);
        }
    }
    ケース2:責任チェーンモデル
    いくつかのシーンでは、1つのオブジェクトに対して一連の作業を行う必要があります.これらの作業はそれぞれ異なるクラスで行われます.この場合、責任チェーンモードを使用するのに適しています.責任チェーンモデルの主な構成要素は3つあります.
  • は、現在のオブジェクトの後続操作オブジェクトを記録するオブジェクトがある操作シーケンスの抽象クラスを管理する.
  • いくつかの特定の操作オブジェクトは、チェーンテーブルとして
  • に整理されます.
  • このモードを使用するクライアントコンポーネントで、複数のオペレータと結合する必要はありません.
  • オブジェクト向けの考え方
    まず、successsorフィールドがオブジェクトの後続操作オブジェクトを管理するために使用される抽象クラスProcessingObjectを定義します.handleインタフェースは対外的にサービスを提供するインタフェースである.handleWorkは実際の処理オブジェクトとしての操作方法である.
    public abstract class ProcessingObject {
    
        protected ProcessingObject successor;
        
        public void setSuccessor(ProcessingObject successor) {
            this.successor = successor;
        }
    
        public T handler(T input) {
            T r = handleWork(input);
            if (successor != null) {
                return successor.handler(r);
            }
            return r;
        }
    
        abstract protected T handleWork(T input);
    }
    次に、次のコードに示すように、2つの具体的な操作オブジェクトを定義できます.PS:ここで「Java 8実戦」という本ではreplaceAllの方法があまり適切ではありません.この点は私たちの前の文章--020:いくつかのStringのAPIとケースを挙げることができます).
    public class HeaderTextProcessing extends ProcessingObject {
        @Override
        protected String handleWork(String input) {
            return "From Raoul, Mario and Alan: " + input;
        }
    }
    
    public class SpellCheckerProcessing extends ProcessingObject {
        @Override
        protected String handleWork(String input) {
            return input.replace("labda", "lambda");
        }
    }
    最後に、Clientでこれらの2つの特定の操作クラスオブジェクトを操作シーケンスとして構成できます.次のコードを参照してください.
    public class Client {
        public static void main(String[] args) {
            ProcessingObject p1 = new HeaderTextProcessing();
            ProcessingObject p2 = new SpellCheckerProcessing();
    
            p1.setSuccessor(p2);
    
            String result = p1.handler("Aren't labdas really sexy?!!");
            System.out.println(result);
        }
    }
    関数式プログラミングの考え方
    関数式プログラミング思考を使用すると、職責チェーンモードは直接的であるy=f(x)とz=g(x)の2つの方法がxを処理するので、この2つの関数を組み合わせるとr=f(g(x))が形成され、つまりLambda式のaddThenを使用して複数の処理プロセスを直列に接続することができる.
    public class ClientWithLambda {
        public static void main(String[] args) {
            UnaryOperator headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
    
            UnaryOperator spellCheckProcessing = (String text) -> text.replace("labda", "lambda");
    
            Function function = headerProcessing.andThen(spellCheckProcessing);
    
            String result = function.apply("Aren't labdas really sexy?!!");
            System.out.println(result);
    
            UnaryOperator hhhhhProcessing = (String text) -> text.concat("hhhh");
            Function function1 = function.andThen(hhhhhProcessing);
            String result1 = function1.apply("Aren't labdas really sexy?!!");
            System.out.println(result1);
        }
    }
    JavaオリジナルのLambda式で実装されたロールチェーンモデルです.前の記事であるvavr:ScalaのようにJavaを書く)で紹介したvavrライブラリを使用して実装することもできます.コードは次のようになります.
    public class ClientWithVavr {
        public static void main(String[] args) {
            Function1 headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
            Function1 specllCheckProcessing = (String text) -> text.replace("labda", "lambda");
    
            Function1 function = headerProcessing.compose(specllCheckProcessing);
            String result = function.apply("Aren't labdas really sexy?!!");
            System.out.println(result);
        }
    }
    まとめ
    関数式プログラミング思考はオブジェクト向けプログラミング思考とは考え方が異なり、表現力が強いことがわかります.そのため、開発者としては関数式プログラミング思考を真剣に学ぶべきです.Java開発者としては、Lambda式から始め、ScalaやKotlinの2つの言語の関数式を勉強して特性にしようと思います.
    参考資料
  • 『Javaプログラミング実戦』
  • 『デザインモードの禅』
  • 本号はバックエンド技術、JVM問題の調査と最適化、Java面接問題、個人成長と自己管理などのテーマに専念し、読者に一線の開発者の仕事と成長経験を提供し、ここで収穫があることを期待しています.