duangsuse::Echo
412 subscribers
3.85K photos
105 videos
574 files
5.15K links
duangsuse技术相干订阅
这是 @duangsuse 与技术有关的发布频道
duangsuse 的另外有 throws 闲杂频道
@dsuset
转载频道 @dsusep
duangsuse 有coding,github,gitlab帐号和bilibili帐号

极小可能会有批评zf的消息 如有不适可以退出

suse的小站:https://piped.stream
ps 另有别名 popf.rip
ʕ•̀ω•́ʔ✧ 🐶🍎🏠生死🐜
(>ω<)岂因祸福避趋之 一鿕
Download Telegram
面向对象基础设计模式小讲堂 🙉 #OOP #learn
我们要介绍 override(覆写), overload(重载, 重新的重) 两个基础多态、以及 Mix-in(混入), Delegate(代理) 这两个设计模式
注:咱不懂 JS 的 prototype chain 也不太懂 Python ,说得概念化一点。
当然我一贯的习性是先讲原理,看官如果讨厌的话就先看后面简单的。 😙

面向对象的基础,除了易语言和其他伪面向对象所谓的 this 参数特殊化处理(最基础的"主语this"封装),关键有三点:
抽象,意味着应该有只指定调用型参(formal parameter)的 interface ,从而允许其他类型覆写。实例如所有东西(门/盒子/...)都可破坏,但破坏的实现是按它的具体属类决定的。
继承,意味着有 Duck: Animal 这种子类型关系,即前者具有后者的所有成员。有了继承和 public(公开)/protected(族内)/private(私下) 的可访问性区分,可以实现对模块外隐藏的代码 抽象-提出为方法 复用,
如把 for (i=0;i<n;i++) op(); 建模为一个 class , i 即成它的私有属性、 op() 即成它的私有方法。如果 op 是构造器参数等可变动项,不就是一种 Iterable 了么🤪
有些语言如 C++ 有多继承,可混入两个超类,但那样对常见模型来说过于复杂
有些语言如 Ruby,Python 有主要支持 mixin 的多继承,(Ruby 的 module Mix; class A < T; extend Mix; Py 的 class A(T, Mix): ) 可以把插入方法解析链上(A.ancestors / A.mro()) 但对构造器调用
多态,本身是「同一名字引用不同含义」的意思,但一般意味着通过子类型的兼容性,你可以在子类里覆盖其超类的方法行为,比如 java.util.function.Function 就严重依赖这个东西(Java 8 的 lambda 当然也基于此)

多态意味着可以创建函数值,它是 Listener 等惯用法所必须的特性(T=Runnable; T oper = new T() { void run(){print(1);} } 这种 "anonymous subclass" 就利用了多态,不然你以为 oper.run() 的 receiver(this=oper) 是怎么知道调用其 T.run 实际做 print(1)
也有人说,对象的本质不是分配出个 struct 存储空间的指针或者 GC object,而是一个强类型的函数式闭包(可依赖外部变量的函数值,这个函数就是 send 方法解析+执行函数、依赖的变量就是 this)

多态的本质是 type checker 的子类型(subtype)兼容+运行时方法解析,方法解析是重点、继承树优化处理点,因为子类型意味着如参数/局部变量可以兼容其要求「表象类型」的所有「实际类型」,如 Animal + Duck

在 Java 里一般用 @Override 标注覆写方法,但这不是必须的。 override(子类型多态) / Generic(parameterized type, 参数化多态) 和 overload 同属多态,但 overload 属于可以静态完成的特殊多态(ad-hoc polymorhism),想想编译器可以通过 type signature 查找的方法简单找到要调用的方法,不需要在运行期基于对象实际类型完成方法派发。

对于 JVM/Ruby/Python 的对象都存有其 type: a.getClass() / a.class / type(a) 所以可以有其VM内建的派发方式,不过在相对底层(无 GC 而用指针)的语言里一般有两种多态方法解析方式: virtual table (虚方法表) / fat pointer (双指针) ;前者意味每个对象都存储其实际类型虚表指针、后者则是每个引用都存储对象的实际类型(优点是局部分配不需要vtable,省内存了)
顺便提一句: Python 目前是 gradual type & consistence type 的语言,分别意味可选标注类型/Any 类型与任意类型自动强制转换
VM:语言用虚拟机(线性指令解释器)/GC:垃圾回收器,分配对象

类(class)有三种基础成员:构造器(constructor)、属性(property, 或者说名词/类层次的 val/var)、方法(method, 或者说动词/类层次的 local fun) ,当然 inner class 和命名空间意义上的 inner class 就是语言特定的了。(基本都是语法糖/特殊命名解析

Java 的属性是用方法实现的,它的 "field" 概念不叫属性,其暴露了过于底层的东西,使得程序员被迫选择是否定义冗余的 private this.x;T getX() ,或者完全放弃可扩展性;这是不重视设计模式优雅性的表现。
一般的 OOP 语言都会支持把 property initializer 和它的类型定义放一起,然后可以集合并生成默认构造器,但记住这只是简写法,构造器应当存在。

方法的继承是基于方法解析,属性继承则是通过构造器调用。无论程序员是否显式写出,超类链(直到Object)上的所有构造器都会被调用,一般而言是超类优先调用,那样子类构造器就能访问超类属性。
所以不允许构造器访问 non-final (open for override) 的属性,因为如果它在子类里被覆盖 超类就会访问到 uninitialized property 。 这种情况属性换成 protected 方法即可。
**更新:如果 initializer 在子类被覆盖就不进行初始赋值,对超类来说也太难判断了。经过测试我才发现那样只是令依赖 override val 的属性得到旧的值:
abstract class A(px:Int) { open val p=px; val p1=p+1 }
class B:A(0) { override val p=2 }
B().p1 //1

open class A { open val p=0; val p1=p+1 }
class B:A() { override val p=1 }
B().p1 //1
当然如果有在构造器里对子类成员的环形引用,是可能访问 uninitialized 的。继承在构造器里必须是单向的。(不必注意 这个不常见)

注意即便对象不是 hash 表(动态对象)实现(Ruby/Python/JavaScript),它和它所有超类的未初始化属性所占用的内存空间也是可以被静态计算出来的,new 将分配和构造器调用同时进行。

https://github.com/duangsuse-valid-projects/Share/blob/master/Others/LuaOOP/oop.lua 这里有一个为 Lua 实现面向对象 new/inherience 的

最基础的封装好多语言都有,我们这次不讲那个可用于支持链式调用传递this上下文的。(因为它的功能局限于此,尽管this成员和global变量的区分隐含一个链结构,那是可编译期静态解决的)
对于不使用 interface 进行"抽象"的返回类型检查,在 Kotlin 里可以用 Nothing (实际上不会返回的操作如 System.exit() / throw Error() 为此类型) 来略过。虽然抽象方法的概念是无需实现体即可声明,它的「实现」与否也是相对其子类而言的。

Mixin 呢,表示隐式(无需指定 receiver)访问不在超类链里方法的设计。假设小明要创建一个 class Duck extends Animal, 但他发现 Animal 里有一群冗余方法可以复用: propertyKeys(), getProperty(), setProperty() 但生成它们返回值的方法需要 super (即 Animal) 对象,而且,因为他在扩展别人已继承 Animal 的类,无法定义出 ProperAnimal 而必须直接继承指定 ? extends Animal
他可以选择复制粘贴,但除了 Duck 他还有一大堆一样的 Animal 子类要定义。不能继承自消除模板化的超类,也不能覆盖超类的抽象方法来提供数据从而复用其逻辑,怎么办?
Java 没有多继承,也没有 Mixin 。 假设它有的话,或许我们可以选择用 Mixin (注意概念上它们全都是 private 方法,不可参与方法解析的)。

interface AnimalBoilerplates extends AnimalKind {
default Set<String> mpropertyKeys() {/**/}
default Object mgetProperty(String key) {/**/}
default void msetProperty(String key, Object value) {}
}
class MoreDuck extends HerDuck implements AnimalBoilerplates {
Set<String> propertyKeys() { return mpropertyKeys(); }
}

当然这个做法也是比较鸡肋的,毕竟是在扩展别人的类库,只是个例子哈哈。
#Kotlin #Java #Lua #design #cs
Delegate 呢,和 java.lang.reflect.Proxy (方法调用拦截) 当然是有区别的啊,不过提到 reflect.Proxy 可能有助于对 Delegate 的形象理解。
class MyDict(private val m: MutableMap<K, V> = mutableMapOf()): Map<K, V> by m
这就是咱漂亮的 Kotlin 对 Delegate 的支持(T by m) 里 T 是任意接口,它会代理其中的成员(从 this)到 m 这个 receiver 上。

负责的老师会告诉面向对象的程序是一群对象相互「发消息(message passing)」的过程,确实是这样;而且 OOPthis(即receiver引用, 或者说"主语") 使得我们以更自然的方法编程和划分代码块,不过发消息的确切说法是「驱使」或「属性访问」 (就是引用某个「主语」上的动词或名词)。

Delegate 在你了解这个事实的时候很直白:它就是把某些方法(调用的"消息")从某个 receiver 上转移给另一个 receiver ,所以我们可以更灵活的利用面向对象的继承,甚至实现"多继承"…… 在上例里,MutableMap 是个无构造器的类,但我们利用 Delegate 使它上的方法(get,set,remove,...) 通通可以访问了 (当然这个"子类型"并不兼容 mutableMapOf() 的实际类型,它是另一个继承自 Any 的新类型,但如果有 m 实现了的 interface 则其方法皆可被代理,这才是"代理"存在的意义)

如果你利用 Delegate, 则可以完美解决上面用 Mixin 很麻烦的复用问题,定义一个需实现的接口(子集)即可 (而且 AnimalBoilerplate 也可以使用 this 外的变量, 同时不局限于访问某 interface 了)。

附赠另两个特色设计模式: Visitor 和 Reducer(非标准) #PLT #ce #OOP

经常写基于 Iterable<T> (map/fliter/sorted/zip/first/...) 的程序吗?那你应该听说过 java.util.stream.Collector :
import java.util.stream.*
Stream.of(1, 2).map { it + 1 }.collect(Collectors.asList())

我最讨厌 Java 的一点就是它废话太多了,聒噪。仔细看 Collector 里有什么? supplier; accumulator:BiConsumer; combiner:BinOperator; finisher ,这就是所谓的 "architect" 到处弄 XXX er/or 的对象,不懂得沉默是金、混淆组合(combine)流与普通映射/过滤流、没有复用 Collection 而是用了所谓的 finisher,咱才懒得看它呢。

Reducer 和 Fold 是另一个版本的 forEach: 假设你是要拿到序列的最大值/最小值(min/max),或者把它化为映射表(associate/groupBy),或者仅仅只是复制一份 List ,但不想写重复代码,你会怎么做?
val xs = listOf(1, 2, 3)
xs.reduce { a, b -> kotlin.math.max(a, b) } // 3
等价这个"折叠"循环: var acc=xs[0]; xs.indices.drop(1).forEach { acc = kotlin.math.max(acc, it) }; acc
当然如果是求和的话,也可以不要求列表至少存1项: xs.fold(0) { a, b -> a+b }
fold
操作往往比 reduce 更实用(如 xs.fold(StringBuilder(), op) ),如果我们把他们结合起来变成对象呢?就成了 Fold/Reducer 。这名词是我自造的 😗

以上操作或依赖一个可变量, 或是 MutableList 什么的。 他们的交集是,都依赖一个(变)量,且在 <T> 流结束的时候,返回结果类型 <R> ,所以必须建模为对象,因为它要构造并持有一个状态并且在 accept(T) 时更新此状态。
我写了泛用的 ConvertReducer<T,R>ModifyReducer<T,A> 两个基类,因为这不是标准设计模式我不赘述,有兴趣找我的代码看,Fold 是 () -> Reducer (R.的构造器类型)。
ParserKt 利用了这个设计模式,从而实现 Repeat(asList()/*asInt(radix=10), asMap,joinAsString...*/, digit) 这种方便的定义式单项构造。

流式编程是来自于函数式 #FP 的智慧,这可以视作它们广为人知的特色(自Lisp就有,地位类似OOP链式调用,不过有了 Kotlin 的 scope function (let/run/apply/also) 后我觉得 Builder 等设计模式要无地自容了😂,流式编程和 MapReduce 却越来越火,超过 LINQ 和 SQL ,嘿嘿),看看你听没听说过这些处理方法: flatMap(trans)/zipWithNext()/first(n)/takeWhile(predicate)/foldRight 。大体上分为 映射(map)/过滤(filterNotNull,first,takeWhile)/排序(sorted,reversed,shuffle)/组合(zip,zipWithNext,interleave)/归纳(fold/associate/flatten/maxBy) 五大类。
注: interleave/shuffle/flatten 应该是不太常见的用法, that's correct ,简单来说就是 交替流/乱序洗牌/铺平嵌套流

顺便一提:猜猜这个是什么: def zipPairs(xs): return zip(xs[0::2], xs[1::2])。当然这么简单定义不了 zipWithNext, 它和 fold (的累积变量)系出同门... 😂
但是 zipWithNext = lambda xs: zip(xs, xs[1:]) 我们仍没有用到重赋值,这证明了函数式的组合表现力,的确应该更新观念了。
" and ".join([f"{a} < {b}" for (a,b) in zipWithNext("1 2 3".split(" "))]) #'1 < 2 and 2 < 3'

很有趣不是吗? 定义式的代码,根本无需数学化的简写语法(点名批评 Py 的 slice)来保持他它们的易读性,这也是 Kotlin 的一大特色。
oop1.lua
1.2 KB
#Lua #oop #PL 🤔刚才体验写标准Android之前又把 LuaOOP 默写了一遍,构造器调用子类优先了,重写后果然感觉优化了不少(也提升了效率编程自信心)
duangsuse::Echo
#Rust #Haskell 反正我现在看 Rust 语法设计也不见得多好…… 但是总还是少点冗余
具体说一下, #Rust 不是 #OOP ,但支持 OOP 写法(成员访问、调用链等)

Rust 不需要 Java/ES6 的模板 constructor ,它是 struct A { fn new() -> A } 的 A::new 命名空间+函数名惯例+#Haskell/JSON 式 Record 构造器 模式,写 impl A {} 第一参关键字 self:&Self 即可(完美替代 #cpp )。方法解析支持静态和 fat pointer 双指针 (vtablePtr+refPtr)

我不满意的地方可能是它的语法太随性(长短不齐、太像数学)吧,而且宏系统的各种变量/类型名也不大优雅

如果要我把 Rust 的设计风格与 Java 的相比,我更讨厌 #Java ,因为它是没学问装逼(冗长),而 Rust 则是过于老学究了,尽管社区很友好。

#Kotlin 是我目前唯一满意的语言设计,虽然感觉实践上 scope function 和 fun= 简写会被一些人滥用。
fold.kt
1.1 KB
#Kotlin #OOP "小王 老猪 阿司马 某A君".split(" ").map(String::length).fold { reduceAll(::minMaxer, ::averager) } == arrayOf(2 to 3, 2)
Forwarded from duangsuse::Echo (duangsuse)
fold.kt
1.1 KB
#Kotlin #OOP "小王 老猪 阿司马 某A君".split(" ").map(String::length).fold { reduceAll(::minMaxer, ::averager) } == arrayOf(2 to 3, 2)
#js #DontKnow 原型链:(感谢 @JackWorks 提供相关信息)

访问语法都是动态解析的,比如 x.prop
x["prop"]
就是
x.[[Get]]("prop", x)

ES 里一共有五种不同的 [[Get]] 实现,分别是
- 普通对象 [规范]
- Argument 对象(你们今天应该不会用到了)
- 类数组对象(数组的特殊行为就在这里)
- 模块对象(import * as Mod 里的 Mod
- Proxy 对象(reflect proxy 全 delegate)

此外, Object.getOwnPropertyDescriptor(o, k) 可以获取可配置 enumerable, writeable 等属性的配置对象
Object.[get/set]PrototypeOf(o)
o.__proto__ 是它的「超类虚表」

[[Get]] 过程的 Receiver (第二参数)很重要,如果没有这个 Receiver,基于原型链的 OOP 其实是做不起来的

原来是往 proto 上找属性!
这就解释了 Array.prototype.map 之类的东西

parent = { method() { return this; } }
child = {}; child.__proto__ = parent;
child.a = 1; child.method(); // 返回自身
最简单的解释是, Receiver 就是属性访问的时候最上层的那个对象,会被当成 this 用。
因为在这个算法里你可以看到,Receiver 是跟着递归一路传递下去的

原来是 o["RESOLVE"](o.prototype, "attr", receiver=o) !(当然,肯定是先查本地,然后才查 prototype
本地如果有就不会查 prototype 了

明白了,和之前写的 LuaOOP 很像,都是层叠属性查找

“ 大佬能交换下原型链相关知识吗
之前看加 Mixin 好像是说把 prototype 除了哪两个属性的什么全部复制一下
#Python#Ruby 的情况我都了解, Py 是 mro() 链查询, A.wtfa.wtf 都是往 class A 找成员,后者实质是 type(a).wtf(a) 所以得到 bound method ,而直接 A.wtf 就是 bind 操作
@staticmethod 直接不收 self ,不需要 bound 所以可以在类和实例上用

https://paste.ubuntu.com/p/tJv7QpSjGt/ liuil-util 的 #TypeScript mixin.ts 重写
duangsuse::Echo
#js #DontKnow 原型链:(感谢 @JackWorks 提供相关信息) 访问语法都是动态解析的,比如 x.prop 或 x["prop"] 就是 x.[[Get]]("prop", x) ES 里一共有五种不同的 [[Get]] 实现,分别是 - 普通对象 [规范] - Argument 对象(你们今天应该不会用到了) - 类数组对象(数组的特殊行为就在这里) - 模块对象(import * as Mod 里的 Mod) - Proxy 对象(reflect proxy 全 delegate)…
让我想起了,那些手写 new/apply/call 的题目 #js #plt #oop
很多人说的 oop 是 Class-oriented programming
而 JavaScript 的原型链才是正宗的 Object-oriented programming(

function X(){}
X.prototype.a=1;
new X().prototype === undefined; // 但 __proto__ 就是 X
Object.getPrototypeOf(X) === Function.prototype
prototype 只是个(被特殊处理的)普通属性
平时说的原型链指的是 obj.[[Prototype]]

a.f()
相当于
f = a.f; f.[[Call]](a, [])

“ 为什么要用 ecmascript 规范里的(原语)写
因为 a.f.call 是从 a.f 往上查了原型链查到 Function.prototype.call 之后,它 也有可能被覆盖掉
Reflect.apply(a.f, a, [])
也能覆盖: globalThis.Reflect = new Proxy(Reflect

” 我就很奇怪为什么我在 constructor 里 this.prototype === undefined

x: 工厂函数
x.prototype: 造实例的原型, new 出来的东西 __proto__

Object.create
, Object.assign
Object.defineProperty