简体   繁体   English

如何在 JavaScript 中将 OOP 转换为 FP

[英]How to convert OOP to FP in JavaScript

I'm having this code in JavaScript我在 JavaScript 中有这个代码

class StringFormatter {

 init(locale) {
  this.locale = locale;
 }

 getA() {
  return 'A' + this.locale
 }

 getB() {
  return 'B' + this.locale
 }

 getC() {
  return 'C' + this.locale
 }

}


// when page just render
let stringManager = new StringFormatter();
stringManager.init('enUS');
// call it in other components 
let result = stringManager.getA();

so when I'm using this OOP class, I only need to set the locale once by init it when page first render.所以当我使用这个 OOP class 时,我只需要在页面第一次渲染时通过初始化locale一次区域设置。 And then all of the later code where I call methods getA, getB or getC will adapt to this locale .然后我调用方法getA, getB or getC的所有后续代码都将适应这个locale

Now as I want to move this to a more Functional Programming way, how can I write it but still keep the same concept.现在,当我想将其转移到更函数式编程的方式时,我该如何编写它但仍保持相同的概念。 Right now I can only come up with this solution现在我只能想出这个解决方案

 getA(locale) {
  return 'A' + locale
 }

 getB(locale) {
  return 'B' + locale
 }

 getC(locale) {
  return 'C' + locale
 }

let result = getA('enUS');

But this one required me to keep passing the locale every time I call the function getA, getB or getC.但这要求我每次调用 function getA, getB or getC.时都必须通过locale

Is there anyway that we somehow still can config the locale but only once , don't need to pass it every time to getA, getB or getC function, and don't need to write in OOP way ?无论如何,我们仍然可以以某种方式配置语言环境,但只能配置一次,不需要每次都将它传递给getA, getB or getC function,并且不需要以 OOP 方式编写

Use a closure:使用闭包:

let getA;
let getB;
let getC;

function initStringFormatter (x) {
    let locale = x;

    getA = function () { return 'A' + locale }
    getB = function () { return 'B' + locale }
    getC = function () { return 'C' + locale }
}

initStringFormatter('enUS');

getA();

In theory, closures can provide exactly the same features as object properties/variables/members in OOP.理论上,闭包可以提供与 OOP 中的 object 属性/变量/成员完全相同的功能。 There's an exact one-to-one relation between closures and objects.闭包和对象之间存在精确的一对一关系。 The only difference is that closures use scope as the mechanism to attach variables to functions whereas objects use bindings to attach variables to methods.唯一的区别是闭包使用 scope 作为将变量附加到函数的机制,而对象使用绑定将变量附加到方法。

Of course, there's nothing wrong mixing OOP with FP.当然,将 OOP 与 FP 混合并没有错。 Even Lisp has an OOP library.甚至 Lisp 也有一个 OOP 库。 So a common javascript idiom is to return an object instead of having the function names as global variables:所以一个常见的 javascript 习惯用法是返回一个 object 而不是将 function 名称作为全局变量:

function initStringFormatter (locale) { // another trick is to just use
                                        // the argument directly instead of
                                        // creating another variable.

    function a () { return 'A' + locale }
    function b () { return 'B' + locale }
    function c () { return 'C' + locale }

    return {
        getA: a,
        getB: b,
        getC: c
    }
}

let stringManager = initStringFormatter('enUS');

stringManager.getA();

Indeed.的确。 It is common to see FP programmers in javascript use objects this way: simply as namespaces for functions. javascript 中的 FP 程序员经常以这种方式使用对象:简单地作为函数的命名空间。 State management can be done 100% using closures instead of object properties. State 管理可以使用闭包而不是 object 属性 100% 完成。

Side note: FP is powerful enough that languages like Lisp don't need OOP to be built-in to the language, instead OOP is a design pattern and/or a library - the standard OOP library for Lisp is CLOS: the Common Lisp Object System Side note: FP is powerful enough that languages like Lisp don't need OOP to be built-in to the language, instead OOP is a design pattern and/or a library - the standard OOP library for Lisp is CLOS: the Common Lisp Object系统

The only downside of using closures is that all closures are essentially private (technically they're not private, they're just local variables to the functions - private/public are binding concepts global/local are scope concepts).使用闭包的唯一缺点是所有闭包本质上都是私有的(从技术上讲,它们不是私有的,它们只是函数的局部变量 - 私有/公共是绑定概念,全局/局部是 scope 概念)。 But if you've done any significant amount of OOP you would recognize this as good practice in the OOP world to not ever give public access to variables.但是,如果您已经完成了大量的 OOP,您会认为这是 OOP 世界中的良好做法,即永远不要让公众访问变量。 When using closures 100% of access to the variable need to be done via functions - again, in the OOP world this would be the common getter/setter design pattern which is considered good practice.当使用闭包时,100% 的变量访问需要通过函数来完成——同样,在 OOP 世界中,这将是常见的 getter/setter 设计模式,被认为是良好的实践。

Note however this is not 100% pure FP.但是请注意,这不是 100% 纯 FP。 But that's OK.但这没关系。 There are other FP languages that are not pure.还有其他一些不纯的 FP 语言。 Javascript still allows enclosed variables to be modified. Javascript 仍然允许修改封闭的变量。 However the above function is pure FP since the locale variable cannot be modified.然而,上面的 function 是纯 FP,因为无法修改locale变量。

To make javascript 100% pure FP is simple.制作 javascript 100% 纯 FP 很简单。 Simply ban all uses of let and var in your code and always use const .只需在代码中禁止所有使用letvar并始终使用const It feels odd at first but you can write any program using only const and function arguments (which are constants by default).一开始感觉很奇怪,但是您可以只使用const和 function arguments (默认情况下是常量)来编写任何程序。 Languages like javascript and Go gives you an escape to use variables to manage state based logic in a straightforward manner.像 javascript 和 Go 这样的语言让您可以使用变量以直接的方式管理基于 state 的逻辑。

You might want a thunk?你可能想要一个thunk?

A thunk delays a calculation until its result is needed, providing lazy evaluation of arguments. thunk 延迟计算直到需要其结果,提供 arguments 的惰性评估。 ( ramda's thunk defination ) ramda 的 thunk 定义

 const thunkGetFromLocale = (locale) => () => (toGet) => toGet + locale let thunk = thunkGetFromLocale('en-US')() console.log(thunk('A')) console.log(thunk('B')) console.log(thunk('C')) thunk = thunkGetFromLocale('vi-VN')() console.log(thunk('A')) console.log(thunk('B')) console.log(thunk('C'))

You can easily create state outside of the function:您可以在 function 之外轻松创建 state:

let locale = 'en-US';

function getA() {
 return 'A' + locale
}

But even though you are using functions now, I'm not sure if it can be called 'functional'.但是即使您现在正在使用函数,我也不确定它是否可以称为“函数式”。

If you modify state outside of the function, you pretty much have similar behavior as a class again, except now it's outside the class.如果您在 function 之外修改 state,则您的行为几乎与 class 再次相似,除了现在它在 function 之外。

If you embrace functional programming, ideally the result of a function should not depend on side-effects.如果您接受函数式编程,理想情况下 function 的结果不应依赖于副作用。

So another way to handle this, is using a higher-order function:所以另一种处理方法是使用更高阶的 function:

function getAForLocale(locale) {
  return () => {
    return 'A' + locale;
  }
}

Now we can call:现在我们可以调用:

const getA = getAForLocale('en-US');

And then we can re-use getA multiple times.然后我们可以多次重复使用 getA 。

I think this is the most functional approach, but I can't say that this is better than using classes.我认为这是最实用的方法,但我不能说这比使用类更好。

Expanding on @slebetman's answer, we can use modern Javascript features to make our closure more concise.扩展@slebetman 的答案,我们可以使用现代 Javascript 功能使我们的闭包更加简洁。

const initStringFormatter = locale => ({
  getA: () => 'A' + locale,
  getB: () => 'B' + locale,
  getC: () => 'C' + locale
});

let stringManager = initStringFormatter('enUS');

stringManager.getA();

You can set a global default that you can pass as a default value for the locale in each function.您可以设置一个全局默认值,您可以将其作为每个 function 中的locale的默认值传递。

Update: If you want to change the defaultLocale ;更新:如果你想改变defaultLocale declare it as let , else keep it const .将其声明为let ,否则保留为const

 let defaultLocale = 'enUS'; const getA = (locale = defaultLocale) => 'A' + locale; const getB = (locale = defaultLocale) => 'B' + locale; const getC = (locale = defaultLocale) => 'C' + locale; let result = getA(); console.log(result); // Output: AenUS defaultLocale = 'fr'; // Change the default locale console.log(getB()); // Output: Bfr


Update: If you want to go the thunk-route as suggested by hgb123, you can pass the function into the thunk.更新:如果您想按照 hgb123 的建议将 go thunk-route 传递给 thunk,您可以将 function 传递到 thunk 中。

 const getA = (locale) => 'A' + locale; const getB = (locale) => 'B' + locale; const getC = (locale) => 'C' + locale; const thunkFormatLocale = locale => () => func => func(locale); let thunk = thunkFormatLocale('enUS')(); console.log(thunk(getA)); console.log(thunk(getB)); console.log(thunk(getC)); thunk = thunkFormatLocale('fr')(); console.log(thunk(getA)); console.log(thunk(getB)); console.log(thunk(getC));

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

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