官方文档
首先需要用openssl创建证书. windows环境下如果找不到openssl, 可以进入bash创建后再exit

openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem

创建中问好几个name,其中问到common name的时候,要填写127.0.0.1, 其他时候用默认值就可以. 完成后在本目录下会生成证书文件
启动https服务:

http-server -S -C cert.pem

鼠标手一般被叫做腕管综合症。这主要是因为鼠标用的是食指和中指,这两个手指的神经是由腕管中的神经来控制。而我的症状则不一样,是小拇指和无名指。我感觉是因为小拇指的负担太重,打键盘太多导致的。小拇指要负责非常多的按键,再加上enter键这个用很多的按键。
因此我想未来鼠标和键盘可能会被淘汰。即使是在生产力工具中,例如笔记本电脑和台式机。因为鼠标和键盘会带来的身体的健康问题,对手指神经的危害,未来势必会被其他的输入方式所取代。一种方式就是语音输入。但是语音输入的准确度不高,并且无法进行中英文混合的准确输入。这对日常办公打中文倒是没有太大问题,然而对于编码的话就问题比较大,还有就是干扰周围的人,并且对噪音非常敏感。还有一种方式就是通过思维。马斯克正在做的脑机接口如果能应用于普通人,则是一个伟大的进步。现在的脑机接口必须嵌入式植入一个芯片在大脑皮层上面。嗯未来如果要普通人使用,则不能用这种侵入式的方式。
尺神经受压通常指的是尺神经综合征(Ulnar Nerve Compression Syndrome),又称肘管综合征(Cubital Tunnel Syndrome)。它是由尺神经在肘部或前臂受压迫造成的,常见症状和相关治疗方法如下:

症状

  1. 麻木与刺痛

    • 小拇指及无名指的麻木或刺痛感,尤其是在手肘屈曲时更为明显。
  2. 疼痛

    • 肘部内侧或前臂内侧可能感到疼痛,通常伴随刺痛感。
  3. 握力减弱

    • 在抓握物体时,可能会发现握力减弱,影响抓握能力。
  4. 运动障碍

    • 有时可能出现手指协调能力下降,例如难以完成细致的动作(如扣扣子)。
  5. 肌肉萎缩

    • 长期受压可导致小拇指根部或无名指根部肌肉萎缩,使得该区域看上去凹陷。

治疗方法

  1. 非手术治疗

    • 休息:避免重复性的手部和肘部活动。
    • 物理治疗:使用拉伸和加强练习,以恢复正常功能。物理治疗师可以帮助设计个性化的锻炼方案。
    • 护具:佩戴护具(如肘托)以保持肘部在良好的位置,特别是在夜间。
    • 药物治疗:使用非处方的消炎药,如布洛芬等,可以缓解疼痛和炎症。
  2. 手术治疗

    • 如果非手术治疗效果不佳且症状持续加重,医生可能建议进行手术。常见的手术方式包括:
      • 尺神经减压术:通过释放对尺神经的压迫。
      • 尺神经转位术:将尺神经移动到一个更少受压迫的位置。

注意事项

如果您有上述症状,请尽快就医,以便进行专业评估并制定相应的治疗计划。尽早干预有助于改善预后。

虽然尺神经受压和腱鞘炎(Tenosynovitis)都是与手部和腕部相关的疾病,但它们是不同的状况。

尺神经受压

  • 定义:尺神经受压通常指的是尺神经在肘部或前臂位置受到压迫,导致其通行受阻,并引发一系列症状,如麻木、刺痛和疼痛。
  • 症状主要包括:小拇指和无名指的麻木感、掌握力减弱以及肘部内侧疼痛等。

腱鞘炎

  • 定义:腱鞘炎是指肌腱周围的腱鞘发生炎症,导致滑动不畅和疼痛。常见于重复性运动或过度使用导致的肌腱疲劳。
  • 症状主要包括:局部疼痛(通常是在手腕或手指部位)、肿胀、触摸时有压痛感,以及活动受限。

主要区别

  • 病因:尺神经受压通常是由于解剖结构问题(如肘部位的压迫),而腱鞘炎通常与过度使用或外伤有关。
  • 出现位置:尺神经受压症状集中在小拇指和无名指,而腱鞘炎则多集中在关节处,如手腕、拇指根部等。

总结

总之,这两种病症虽有相似之处,但应根据具体的症状及体检结果进行区分。如有疑问或出现相关症状,请咨询医生以获得准确诊断和治疗建议。

一种是页面json不变, 使用css属性position: fixed;,同时规定css属性 top bottom left right.
第二种是可以在页面的json文件中写: "disableScroll": true, 然后再元素上使用css属性position: absolute;,再规定css属性top bottom left right. 这种方式是结合recycle-view来用的, 原因太久不太记得了, 似乎因为Recycle-view的一些特性导致position:fixed不能正常固定或页面不能正常滚动.

进入一个页面有几种方式: navigateTo,navigateBack, 熄屏再显示(从其他应用切换回来也是类似).
GPT给的建议是通过app.globalData存储全局变量来判断, 或者用storage存储判断, 感觉都不优雅, 把单个页面问题的解决扩展到了全局去了. 但目前还没有其他好办法, 因为navigateBack和熄屏再显示都是触发onHide,所以无法区分.
GPT还给了一个幻觉建议, 说onShow会带形参进来,判断形参就可以, 但事实上onShow不会带任何形参
通过navigateTo,navigateBack从其他页面导航到这个页面, 通常都需要更新页面数据. 因为上一页导航过来, 这一页的数据还没有生成过; 从下一页返回, 可能在下一页对数据做了更新.

因为CSS的flex每次用的时候总是忘记, 这儿总结一下:
将flex理解为一个画框(父元素)里面的元素(子元素)的排列.
父元素设置整体遵从的规则, 子元素设置个别规则.
总体规格包括:

  • flex-direction: 确定主轴方向. 元素是沿着主轴方向依次放置的. 交叉轴(副轴)即主轴的垂直轴, 确定一行主轴放不下的元素,下一行放的方向.
  • flex-wrap: 元素在主轴放不下, 要不要回车放在下一行.
  • justify-content: 确定元素主轴排列, 以及元素间距
  • align-items: 确定副轴的元素排列
  • align-content: 确定wrap后多行的元素怎么放置,以及行间距等.

银行送了10元京东E卡,进了京东APP完全找不到绑定的地方, 搜索兰搜索京东E卡也只是给了一堆的卖E卡的连接. 说是付款的时候京东自营商品可用, 然后下了好几个单,专门找京东自营,充话费,京东超市和京喜自营买纸巾,付款的时候都没有选择E卡的界面. 找了一圈只好问机器客服, 这才找到绑卡的地方: 我的钱包-点击查看全部-礼品卡,此时注意不要点那个大大的京东E卡图标, 而要点击绑定新卡! 然后输入卡的密码, 只用输入密码, 而不用输入卡号, 虽然给了卡号但并没有什么用.
然后, 我去找下单但没有付款的链接, 还是不能选择! 接着下新单, 也没有选E卡的地方! 我勒个去, 赶快百度找下经验, 发现百度经验这个下单支付界面怎么和我不一样? 终于发现了, 原来从购物车购买,点击去结算才有这个选礼品卡(京东卡/E卡)订单界面, 这个订单界面选择地址, 查看订单详情,选择开票等等.
从商品链接直接购买真的不能选择吗? 我又研究了下,发现不是的!商品链接里面直接点击下单购买(这个名字在不同的商品链接还不一样, 有的叫到手价购买, 有的叫立即购买预估), 这时候不会转到新页面,而是从下方弹出来一个半屏, 上面显示收货地址,选择商品数量和规格, 平常这个时候就选"支付"了, 但注意, 这个半屏是可以上滑的! 你如果上滑一点点, 发现滑出来的只是其他商品的广告, 但如果继续上滑, 才出现商品价格详情,支付方式,发票,留言...等等这些订单信息, 全部堆在这个半屏的最下面, 跟从购物车结算的订单界面是类似的. 但是等等, 为什么礼品卡的选择消失了? 哈哈,这时候还要去选择商品金额栏目里的四个灰色小字:展开更多,才看得到优惠券礼品卡两个选项,不得不说比购物车结算订单界面要隐蔽得多.
京东这个界面设计真的很滑头, 其逻辑是想方设法让你别用券直接支付, 让你确认地址/品类/数量这些最重要信息后就直接付款, 这样 一方面京东拿到最多的钱, 另一方面你买了以后即使想到有优惠券, 也只能下次用了, 又引诱你下次继续在京东购物. 之前几次买东西找不到开票的地方也是这个原因.

引用自定义组件

组件名 组件json-> 页面json-> 页面wxml
mycomp.js/.json/.wxml/.css {"component": true,usingComponents": {}} {"usingComponents": {"mycomp": "../../components/mycomp/mycomp"}} <mycomp></mycomp>

组件展现页面的数据和自身的数据

没有区分. 如

`Component({
  properties: {propFromPage: 1},
  data: {propFromData: 2}
})

在组件wxml中都是同样方式引用{{propFromPage}} {{propFromData}}. 但在组件js中引用方式有区分:this.properties.propFromPage, this.data.propFromData

调用函数

组件中的函数引用方式在js中不需要加methods, 如

`Component({
  properties:{prop:{type: Number, value:1, observer(newValue, oldValue){this.f1();}}},
  lifetimes:{attached(){this.f1();}},
  methods: {
    f1(){},
    f2(){this.f1()},
  }
})

页面向组件传数据

页面js-> 页面wxml-> 页面wxml 组件js
Page({data:{pageprop: 1}}) <mycomp prop="{{pageprop}}"></mycomp> {{prop}} Component({ properties: {prop: 默认值}, methods:{f(){this.properties.prop },}})

组件向页面传数据(通过事件)

组件js-> 页面wxml-> 页面js
Component({method:{f(){this.triggerEvent('someevent', somevalue) },}}) <mycomp bind:someevent="processIt"></mycomp> processIt(e){let value = e.detail}

GPT4o给了三种方法, 在后面给出. 第三种方法是改赋值=为调用函数, 意味着所有的赋值都要改为函数调用,不实用. 分析下方法一Proxy和方法二getter/setter.
Proxy的好处是不会改变原对象,而是建立了一个新的proxy对象, 对新对象的赋值会触发set函数. 问题有三个: 一是任何键值变化都会触发set,需要自己判断键值; 二是需要将原对象的名称指向新proxy对象, 才能实现无感. 三是无法通过instanceof判断pr一个实例是不是Proxy的实例, 只能自己在创建实例时添加一个自定义key如_isProxy来判断.(后面段落说明)

getter/setter是最早一代vue.js用的方法, 所有在app.data中的键都会用Object.defineProperty()的get/set重定义一遍, 做到自动更新UI. 好处是可以逐个key定义,不影响其他key, 问题是原key值就会消失,除非定义前先保存一下. 并且需要保存到另一个键下面(一般前面加个下划线_)而不能保存在本key下面, 否则会导致反复触发直到调用栈溢出Maximum call stack size exceeded.
这个话题在vue的官方文档中也有讨论: https://cn.vuejs.org/guide/extras/reactivity-in-depth, 里面也提到在vue3中,从getter/setter已经转向了proxy机制.
另外一个问题, 是如果键值是一个对象(数组也是),也就是下面有子键, 对子键键值的修改, 只会调用get而不是set!并且对Proxy和getter/setter机制都一样. 如:

proxyObject.k = {k1:1} // 调用set
proxyObject.k.k1 = 2 //调用get

怎么解决这个问题,GPT4o的回答是递归代理. 见后面一节.

方法1: 使用Proxy

在 JavaScript 中,如果希望在对象的某个键变化时自动调用一个函数,可以使用 Proxy 对象来实现。Proxy 允许你创建一个代理对象,以便对其属性的访问和修改进行拦截。

下面是一个使用 Proxy 的示例代码,当对象的 someKey 属性变化时,会自动调用函数 a

// 定义要监控的对象
const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 创建 Proxy 代理对象
const proxyObject = new Proxy(targetObject, {
    set(target, property, value) {
        // 当设置 someKey 时,调用函数 a
        if (property === 'someKey') {
            a(value);
        }
        // 修改属性的值
        target[property] = value;
        // 返回 true 表示设置成功
        return true;
    }
});

// 测试:修改 someKey
proxyObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 可以继续修改其他属性也不会触发 a()
proxyObject.anotherKey = '另一个值';
console.log(proxyObject.anotherKey); // 输出: 另一个值

说明:

  • 在上面的示例中,创建了一个对象 targetObject 和一个函数 a
  • 使用 Proxy 创建了代理对象 proxyObject,并在其 set 拦截器中定义了逻辑。当 someKey 被修改时,会自动调用函数 a(newValue)
  • 修改其他属性不会触发这个函数,只会在指定的键发生变化时执行。

通过这种方式,你可以灵活地监控对象属性的变化,并根据需要触发相应的逻辑。

方法2 使用getter/setter

你可以使用 JavaScript 的 Object.defineProperty() 方法来定义 getter 和 setter,使得每次修改对象属性时都能执行相应的函数。

const targetObject = {
    _someKey: '初始值',  // 私有变量
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 使用 Object.defineProperty 定义 getter 和 setter
Object.defineProperty(targetObject, 'someKey', {
    get() {
        return this._someKey;
    },
    set(value) {
        this._someKey = value;
        a(value);  // 调用函数 a
    },
});

// 测试:修改 someKey
targetObject.someKey = '新的值'; // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 可以继续修改其他属性也不会触发 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: undefined (因为没有定义)

方法3:使用自定义方法

另一种简单的方法是创建一个用于更新对象属性的方法,以便所有更改都通过此方法发生,这样就可以在里面调用相关逻辑。

const targetObject = {
    someKey: '初始值',
};

// 定义要自动调用的函数
function a(newValue) {
    console.log(`someKey 的新值是: ${newValue}`);
}

// 自定义方法用于更新键并执行相应逻辑
function setSomeKey(value) {
    targetObject.someKey = value;   // 更新属性
    a(value);                       // 调用函数 a
}

// 测试:修改 someKey
setSomeKey('新的值'); // 这将输出: someKey 的新值是: 新的值

// 读取 someKey
console.log(targetObject.someKey); // 输出: 新的值

// 修改其他属性的方法不变,不会触发函数 a()
targetObject.anotherKey = '另一个值';
console.log(targetObject.anotherKey); // 输出: 另一个值

如何知道实例是Proxy的实例

在 JavaScript 中,可以使用 Proxy 对象来创建代理,从而控制对某个对象的基本操作,如属性查找、赋值、枚举、函数调用等。如果想要判断一个对象是否是一个 Proxy 实例,可以通过以下方法:

  1. 利用 WeakMap: 在将对象包装为 Proxy 时,可以使用 WeakMap 来存储原始对象和其代理之间的关系。通过这种方式,你可以检查某个代理是否存在于这个 WeakMap 中。

     const originalObject = {};
     const proxyMap = new WeakMap();
    
     const proxy = new Proxy(originalObject, {
         set(target, prop, value) {
             return Reflect.set(target, prop, value);
         }
     });
    
     // 存入 WeakMap
     proxyMap.set(proxy, originalObject);
    
     function isProxy(obj) {
         return proxyMap.has(obj);
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  2. 标记法: 你可以在创建 Proxy 时在其内部设置一个标记属性,以便后续判断。

     const originalObject = {};
    
     const proxy = new Proxy(originalObject, {
         get(target, prop) {
             if (prop === '_isProxy') return true;
             return Reflect.get(target, prop);
         }
     });
    
     function isProxy(obj) {
         return obj && obj._isProxy === true;
     }
    
     console.log(isProxy(proxy)); // true
     console.log(isProxy(originalObject)); // false
  3. 使用 instanceof 关键字: 不幸的是,直接通过 instanceof 来判断是否为 Proxy 是不可能的,因为我们无法直接访问 Proxy 的构造函数。

总的来说,上述方法是较常见的判断一个对象是否为 Proxy 的方案。选择适合自己需求的方法来实现即可。

递归代理

要解决object下的key是对象或者数组, 其子键或者子元素被修改时调用的是get而不是set的问题, GPTo答复是递归代理. 下面给出的是对象的递归代理, 实际还需要考虑数组.
递归代理是对了,但是GPT4o在第一次给出的代码中并没有实现递归代理,指出后,在第二次实现了递归代理, 但设置深层子元素的时候调用了2次set(重复代理了),指出问题后, GPT4o再修改了6次, 也没能回答正确. 感觉是黔驴技穷啦. 这儿贴出其中一次的代码.

function createReactiveObject(obj) {
    return new Proxy(obj, {
        set(target, property, value) {
            // 检查设置的值是否为对象
            if (typeof value === 'object' && value !== null) {
                // 用 Proxy 处理该对象
                value = createReactiveObject(value);
            }
            // 设置实际值
            target[property] = value;
            console.log(`属性 ${property} 被设置为: ${value}`); // 输出变更信息
            return true; // 返回成功标识
        },
        get(target, property) {
            const value = target[property];
            // 仅当获取的是对象时才返回新的代理,避免递归代理产生重复
            if (typeof value === 'object' && value !== null) {
                return createReactiveObject(value); // 利用递归返回新的代理而不是多次创建同一对象的代理
            }
            return value;
        }
    });
}

// 创建动态对象并使用代理函数包裹它
const dynamicObject = createReactiveObject({
    k1: { k2: { k3: {} } } // 初始化嵌套结构 
});

// 测试:设置嵌套属性
dynamicObject.k1.k2.k3.k4 = 20; // 应只输出一次: 属性 k4 被设置为: 20

// 打印查看当前状态
console.log(dynamicObject); 

node版本

作为tools.js的功能, 代码如下

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

  // 事件发射
  emitEvent(event, arg){
    eventEmitter.emit(event, arg)
  },

  //等待事件, 成功则返回args, 失败则返回null
  async waitEvent(event, timeout = 500){
    return new Promise((resolve, reject) => {
      let timer = setTimeout(() => {
        reject(timeout) // 如果用await waitEvent(),则抛出异常值为timeout
      }, timeout)
        // 监听消息事件
      const messageListener = (arg) => {
          clearTimeout(timer); // 清除定时器
          eventEmitter.removeListener(event, messageListener); // 移除监听器
          resolve(arg); // 解决Promise,返回数据
      };

      eventEmitter.on(event, messageListener); // 添加监听器
    })
  }

使用示例:

f = async ()=>{
  setTimeout(()=>tools.emitEvent('laugh',"hahaha"), 100)
try{
  let res = await tools.waitEvent('laugh', 500)
  console.log("res is", res)
  }catch(e){console.log('timeout', e)}
}
f() // 打印: res is hahaha

上一篇写了面向对象编程的几个原则, SOLID, 思想原则是很好的, 也就是无论一个模块功能怎么变, 都不能让别的模块非得跟着自己一起变. 为了实现这个效果, 要借助接口interface这个特定于一系列面向对象编程设计的语言, 如Java, C#, 并作为这些语言的基石. 其他一些语言如Go, Kotlin, Swift, Typescript, PHP也有interface, C++没有interface,但据说virtual/template可以实现类似功能(根据GPT4回答).
但总觉得interface这个思想是好的,实现起来很啰嗦. 这儿又看到一篇文章,里面推崇函数式编程,而嫌弃面向对象编程: 函数式编程入门经典
在知乎的这篇问答中, 其中有个回答翻译了Why OO Sucks的全文, 也值得一看.
总的来说, 函数式编程认为数据和函数必须分离,面向对象编程认为两者必须绑定. 从实现来说, 函数式编程往往比面向对象要简洁得多, 这一点我更喜欢.
事实上语言设计之初就是从某种思想出发的, 最主流的就是面向过程,面向对象和函数式编程, 问了GPT4o这个问题, 回答如下:

编程语言可以根据其设计理念和主要特性分为几类,包括面向对象编程(OOP)、面向过程编程(POP)和函数式编程(FP)。以下是对这些分类的详细说明:

问: 那些语言是面向对象编程的思想设计的,哪些是面向过程的思想设计的,哪些是函数式编程的思想设计的

面向对象编程语言 (OOP)

面向对象编程强调“对象”的概念,通过封装、继承和多态性来组织代码。常见的面向对象编程语言包括:

  • Java
  • C++
  • Python(同时支持多种范式,包括OOP)
  • C#
  • Ruby

面向过程编程语言 (POP)

面向过程编程关注于以过程或功能为中心,通过调用函数来执行任务。常见的面向过程编程语言包括:

  • C
  • Pascal
  • Fortran
  • BASIC

函数式编程语言 (FP)

函数式编程强调使用数学函数的方式,避免状态改变和可变数据。常见的函数式编程语言包括:

  • Haskell
  • Lisp (及其方言,如 Scheme)
  • Erlang
  • F#

其他混合范式语言

有些现代语言结合了多种编程范式,不局限于某一种。例如:

  • Python:支持面向对象、面向过程和部分函数式特性。
  • JavaScript:既可写成面向对象,也可实现函数式风格。
  • Scala:结合了面向对象与函数式特性。

这些分类帮助开发者根据项目需求选择合适的工具与方法来实现解决方案。

问: 除了OOP,POP,FP这几种设计理念, 还有其他设计理念吗,代表的编程语言有哪些

除了面向对象编程(OOP)、面向过程编程(POP)和函数式编程(FP)这三种主流设计理念外,还有其他几种重要的编程设计理念及其代表的编程语言:

1. 逻辑编程 (Logic Programming)

逻辑编程基于形式逻辑,通过声明“什么是”而非“如何做”,使用规则和事实来推理。代表的编程语言包括:

  • Prolog
  • Mercury

2. 声明式编程 (Declarative Programming)

声明式编程关注于描述要完成的任务,而非具体的控制流程。它强调“是什么”而不是“怎么做”。代表的语言包括:

  • SQL(结构化查询语言,主要用于数据库查询)
  • HTML(超文本标记语言,用于网页内容表示)

3. 并发/并行编程 (Concurrent/Parallel Programming)

并发/并行编程涉及多个计算过程同时运行,提升程序执行效率。代表的语言包括:

  • Go:具有内置的并发支持。
  • Erlang:以其强大的并发特性著称,广泛用于实时系统。

4. 反应式编程 (Reactive Programming)

反应式编程是一种异步数据流处理方式,可以自如应对时间变化或事件变化。代表的语言或框架包括:

  • RxJava:Java中的反应式扩展。
  • Scala:结合了函数式和反应式编程特性。

5. 数据流编程 (Dataflow Programming)

数据流编程将计算看作数据在节点之间传递的过程,更关注数据流动而非控制流。代表性的工具和环境,包括:

  • LabVIEW
  • Apache NiFi

6. 面向方面编程 (Aspect-Oriented Programming, AOP)

面向方面编程通过关注横切关注点,将其与业务逻辑分离,以减少重复代码和提高模块化。代表的语言包括:

  • AspectJ:是一种基于Java的面向方面扩展。

这些设计理念为解决特定问题提供了不同的方法和思路,开发者可以根据需求灵活选择合适的方法论进行开发。

简称SOLID

单一职责原则 (SRP):

每个类应该只有一个职责,避免承担过多功能。

开放-关闭原则 (OCP):

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
我的理解: 新增功能的时候, 是新增函数就能实现(即可以扩展), 而不是要修改已有函数才能实现, 因为修改已有函数, 会改变函数行为, 导致已经依赖此函数的其他模块的行为不可预测. 说到底,就是你改你的就别影响别人.

里氏替换原则 (LSP):

子类应该能够替换父类而不影响程序的正确性。
我的理解: 子类应保持父类函数的意图.

接口隔离原则 (ISP):

不应该强迫客户依赖他们不需要的接口。
我的理解: 因为接口内所有函数都是是必须实现的(C++?), 所以如果定义的一个接口里有太多要实现的函数, 客户(底层模块)又不需要, 就不胜其烦. 所以接口作为系统平台, 紧密关联的一组函数可以作为一个接口, 而相对独立的作为另一个接口, 客户可以选择实现哪个.

依赖倒置原则 (DIP):

高层模块不应依赖于低层模块,两者都应依赖于抽象(接口)。
我的理解: 接口相当于系统平台, 而模块无论是高层还是低层, 都相当于和平台打交道的人. 没有系统平台的情况下, 人和人直接打交道,无论是上下级(DIP原则说的是上下级)还是平级, 都可能是不规范的, 随时会变更自己交给别人的数据形态的, 这样一个人提交的数据变了另一个人即使没做错任何事都得跟着变, 就很讨厌. 通过系统平台, 就不会把变化溢出到其他人那里.
具体到实现, 就是无论高低层模块, 都与接口打交道, 低层实现接口, 高层调用接口, 而不是直接调用低层的类.