面向對象編程 —— java實現函數求導

来源:http://www.cnblogs.com/thinkam/archive/2017/12/02/7933279.html
-Advertisement-
Play Games

文章目錄 ★引子 ★求導 ★最初的想法 ★初步的想法 ★後來的想法 ★最後的想法 ★編程範式 ★結尾 首先聲明一點,本文主要介紹的是面向對象(OO)的思想,順便談下函數式編程,而不是教你如何準確地、科學地用java求出函數在一點的導數。 ★引子 首先,直接上一段python代碼,請大家先分析下上面代 ...


文章目錄

★引子

★求導

★最初的想法

★初步的想法

★後來的想法

★最後的想法

★編程範式

★結尾

 

首先聲明一點,本文主要介紹的是面向對象(OO)的思想,順便談下函數式編程,而不是教你如何準確地、科學地用java求出函數在一點的導數。

 

★引子

 

def d(f) :
    def calc(x) :
        dx = 0.000001  # 表示無窮小的Δx
        return (f(x+dx) - f(x)) / dx  # 計算斜率。註意,此處引用了外層作用域的變數 f
    return calc  # 此處用函數作為返回值(也就是函數 f 的導數)
# 計算二次函數 f(x) = x2 + x + 1的導數
f = lambda x : x**2 + x + 1  # 先把二次函數用代碼表達出來
f1 = d(f)# 這個f1 就是 f 的一階導數啦。註意,導數依然是個函數
# 計算x=3的斜率
f1(3)
# 二階導數
f2 = d(f1)

首先,直接上一段python代碼,請大家先分析下上面代碼是用什麼方法求導的。請不要被這段代碼嚇到,你無需糾結它的語法,只要明白它的求導思路。

以上代碼引用自《為啥俺推薦 Python[4]:作為函數式編程語言的 Python》,這篇博客是促使我寫篇文章的主要原因。

博主說“如果不用 FP,改用 OOP,上述需求該如何實現?俺覺得吧,用 OOP 來求導,這代碼寫起來多半是又醜又臭。”

我將信將疑,於是就用面向對象的java試了試,最後也沒多少代碼。如果用java8或以後版本,代碼更少。

請大家思考一個問題,如何用面向對象的思路改寫這個程式。請先好好思考,嘗試編個程式再繼續往下看。

考慮到看到這個標題進來的同學大多是學過java的,下麵我用java,用面向對象的思路一步步分析這個問題。

 

求導

 

文章開頭我已近聲明過了,本文不是來討論數學的,求導只是我用來說明面向對象的一個例子。

如果你已經忘了開頭那段代碼的求導思路,請回頭再看看,看看用python是如何求導的。

相信你只要聽說過求導,肯定一眼就看出開頭那段代碼是用導數定義求導的。

代碼中只是將無窮小Δx粗略地算做一個較小的值0.000001。

 

最初的想法

 

//自定義函數
public class Function {
    //函數:f(x) = 3x^3 + 2x^2 + x + 1
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}
//一元函數導函數
public class DerivedFunction {
    //表示無窮小的Δx
    private static final double DELTA_X = 0.000001;
    //待求導的函數
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    /**
     * 獲取function在點x處的導數
     * @param x 待求導的點
     * @return 導數
     */
    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}
public class Main {
    public static void main(String[] args) {
        //一階導函數
        DerivedFunction derivative = new DerivedFunction(new Function());
        //列印函數在x=2處的一階導數
        System.out.println(derivative.get(2));
    }
}

先聲明一點,考慮到博客篇幅,我使用了不規範的代碼註釋,希望大家不要被我誤導。

我想只要大家好好思考了,應該至少會想到這步吧。代碼我就不解釋了,我只是用java改寫了文章開頭的那段python代碼,做了一個簡單的翻譯工作。再請大家考慮下以上代碼的問題。

剛開始,我思考這個問題想到的是建一個名為Function的類,類中有一個名為f的方法。但考慮到要每次要求新的函數導數時就得更改這個f方法的實現,明顯不利於擴展,這違背了開閉原則

估計有的同學沒聽過這個詞,我就解釋下:”對象(類,模塊,函數等)應對擴展開放,但對修改封閉“。

於是我就沒繼續寫下去,但為了讓大家直觀的感受到這個想法,我寫這篇博客時就實現了一下這個想法。

請大家思考一下如何重構代碼以解決擴展性問題。

 

初步的想法

 

估計學過面向對象的同學會想到把Function類改成介面或抽象類,以後每次添加新的函數時只要重寫這個介面或抽象類中的f方法,這就是面向介面編程,符合依賴反轉原則,下麵的代碼就是這麼做的。

再聲明一點,考慮到篇幅的問題,後面的代碼我會省去與之前代碼重覆的註釋,有不明白的地方還請看看上一個想法中的代碼。

//一元函數
public interface Function {
    double f(double x);
}
//自定義的函數
public class MyFunction implements Function {
    @Override
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}
public class DerivedFunction {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}
public class Main {
    public static void main(String[] args) {
        //一階導函數:f'(x) = 9x^2 + 4x + 1
        DerivedFunction derivative = new DerivedFunction(new MyFunction());
        System.out.println(derivative.get(2));
    }
}

我想認真看的同學可能會發現一個問題,我的翻譯做的還不到位,開頭那段python代碼還可以輕鬆地求出二階導函數(導數的導數),而我的代碼卻不行。

其實只要稍微修改以上代碼的一個地方就可以輕鬆實現求二階導,請再思考片刻。

 

後來的想法

 

當我寫出上面的代碼時,我感覺完全可以否定“用 OOP 來求導,這代碼寫起來多半是又醜又臭”的觀點。但還不能求二階導,我有點不甘心。

於是我就動筆,列了一下用定義求一階導和求二階導的式子,想了想兩個式子的區別與聯繫,突然想到導函數也是函數。

DerivedFunction的get方法和Function的f方法的參數和返回值一樣,DerivedFunction可以實現Function介面,於是產生了下麵的代碼。

public interface Function {
    double f(double x);
}
public class DerivedFunction implements Function {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    @Override
    public double f(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}
public class Main {
    public static void main(String[] args) {
        Function f1 = new DerivedFunction(new Function() {
            @Override
            public double f(double x) {
                return 3 * x * x * x + 2 * x * x + x + 1;
            }
        });
        System.out.println(f1.f(2));
        //二階導函數:f''(x) = 18x + 4
        Function f2 = new DerivedFunction(f1);
        //列印函數f(x) = 3x^3 + 2x^2 + x + 1在x=2處的二階導數
        System.out.println(f2.f(2));
    }
}

考慮到有的同學沒學過java8或以上版本,以上代碼沒有用到java8函數式編程的新特性。 

如果你接觸過java8,請考慮如何改寫以上代碼,使其更簡潔。

 

最後的想法

 

public class DerivedFunction implements Function<Double, Double> {
    private static final double DELTA_X = 0.000001;
    private Function<Double, Double> function;

    public DerivedFunction(Function<Double, Double> function) {
        this.function = function;
    }

    @Override
    public Double apply(Double x) {
        return (function.apply(x + DELTA_X) - function.apply(x)) / DELTA_X;
    }
}
public class Main {
    public static void main(String[] args) {
        //列印函數在x=2處的二階導
        System.out.println(new DerivedFunction(new DerivedFunction(x -> 3 * x * x * x + 2 * x * x + x + 1)).apply(2.0));
    }
}

之前幾個想法為了擴展Function介面,使用了外部類、匿名類的方式,其實也可以用內部類。而這在這裡,我用了lambda表達式,是不是更簡潔了。

這裡用的Function介面用的是jdk自帶的,我們不需要自己定義了。因為這是一個函數式介面,我們可以用lambda方便地實現。後來發現,其實這裡用UnaryOperator這個介面更恰當。

現在大家有沒有發現,用java、用OOP也可以非常簡潔地實現求導,並不比開頭的那段python代碼麻煩很多。

 

★編程範式

 

在我看來,編程範式簡單來說就是編程的一種模式,一種風格。

我先介紹其中的三個,你差不多就知道它的含義了。

面向對象程式設計(OOP)

看到這裡的同學應該對面向對象有了更直觀的認識。在面向對象編程中,萬物皆對象,抽象出類的概念。基本特性是封裝、繼承、多態,認識不深的同學可以再去我之前的代碼中找找這三個特性。

我之前還介紹了面向對象的幾個原則:開閉原則依賴反轉原則。其他還有單一職責原則里氏替換原則介面隔離原則。這是面向對象的5個基本原則,合稱SOLID

函數編程語言(FP)

本文開頭那段代碼用的就是python函數式編程的語法,後來我又用java8函數式編程的語法翻譯了這段代碼。

相信你已經直觀地感受到它的簡潔,以函數為核心,幾行代碼就解決了求導的問題。

過程式編程(Procedural programming)

大概學過編程都學過C,C語言就是一種過程式編程語言。在我看來,過程式編程大概就是為了完成一個需求,像記流水帳一樣,平鋪直敘下去。 

       

結尾

 

由於本人初學java,目前只能想到這麼多。如果大家有更好的想法或者覺的我上面說的有問題,歡迎評論,望各位不吝賜教。

這是我的第一篇技術博客,但願我說清楚了面向對象。如果對你有幫助,請點個贊或者評論下,給我點繼續創作的動力。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、使用final關鍵詞修飾一個變數時,是引用不能變,還是引用的變數不能變? 使用final關鍵字修飾一個變數時,是指引用變數不能變,引用變數所指向的對象中的內容還是可以改變的。例如,對於如下語句:final StringBuffer a=new StringBuffer("immutable"); ...
  • 自定義模塊 模塊由什麼組成 npm 如何發佈自己的模塊 常見的命令: require 引入模塊 exports 輸出模塊 (想對外輸出東西時,必須加上exports) module.exports 批量輸出模塊 require 1.有‘./’ 從當前目錄中招 2.沒有‘./’ 先從系統模塊找,再從n ...
  • 1. 表達式語言OGNL OGNL簡介 OGNL基本語法 常量 操作符 OGNL表達式 OGNL基礎 OGNL上下文 OGNL值棧 OGNL的訪問 常量 操作符 OGNL表達式 OGNL上下文 OGNL值棧 OGNL的訪問 2. 具體內容 2.1 OGNL簡介 OGNL(Object-Graph N ...
  • 最近在學習workerman的時候比較頻繁的接觸到回調函數,使用中經常會因為worker的使用方式不同,會用這兩種不同的方式去調用外部的worker變數,這裡就整理一下PHP閉包獲取外部變數和global關鍵字聲明變數的區別。 閉包 閉包是一個常見的概念,我們通常可以將其與回調函數配合使用,可以使代 ...
  • 定義: 將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。生成器模式利用一個導演者對象和具體建造者對象一個一個地建造出所有的零件,從而建造出完整的對象。 四個要素: Builder:生成器介面,定義創建一個Product對象所需要的各個部件的操作。 ConcreteBuild ...
  • 一、kindedit編輯器 就是上面這樣的編輯輸入文本的一個編輯器 這也是一個插件。那麼怎麼用呢? 1、下載:百度kindedit 2、引入: 3、看官方提供的文檔 在addarticle.html中 當你把編輯器插入好的時候,你看似都可以設置大小,字體變粗等。。但是當你上傳圖片的時候就會想下麵一樣 ...
  • [toc] 功能和特性 基於socket實現的c/s架構的的通信 伺服器和客戶心跳連接 gson實現的消息通信機制 註冊及登錄 支持私聊和群聊。 動態更新用戶列表以及用戶消息提示 支持emoji表情,以及emoji表情選擇器 伺服器端資料庫用戶記錄 ~~實現文件傳輸~~ ~~文件記錄~~ 功能展示 ...
  • 寫什麼?為什麼寫?寫給誰看?這個是寫博客的首要問題。 寫什麼? 在這個分類下的文章主要是講一些博主對於JDK9的源碼理解結合一些入門級的數據結構與演算法。引用和知識來源主要來源於 CLRS(演算法導論),Alogrithms( 演算法 普林斯頓大學教材 鏈接:https://algs4.cs.prince ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...