小程序中的大道理之三--对称性和耦合问题

再继续扒

继续 前一篇 的话题, 在那里, 提到了抽象, 耦合及 MVC, 现在继续探讨这些, 不过在此之前先说下第一篇里提到的对称性.

注: 以下讨论建立在前面的基础之上, 为控制篇幅起见, 这里将不再重复前面说到的部分, 如果您还没看过前两篇章, 阅读起来可能会有些困难.

这是第一篇的链接小程序中的大道理

对称性(Symmetry)

这里先说下对称性的问题. 问题中的图案是左右对称的, 但到目前为此, 我们的代码却不是对称的, 输出之所以对称原因在于空格与背景之间难以区分.

让我们换个符号, 比如脱字符"^", 再输出一下, 就可以看出不对称来, 因为在右边我们输出星号后就直接换行了:

star and caret not symmetry

只要稍微调整一下程序, 在输出换行之前再输出一下空格(或者现在的脱字符"^"), 就能满足对称的输出了:

private String getLineContent(int lineCount, int lineNumber) {
    StringBuilder content = new StringBuilder();
    
    String firstPart = getFirstPart(lineCount, lineNumber);
    // 1. 空格部分
    content.append(firstPart);
    // 2. 星号部分
    content.append(getSecondPart(lineNumber));
    // 3. 空格部分
    content.append(firstPart);
    // 4. 换行部分
    content.append(System.lineSeparator());
    
    return content.toString();
}

这样之后, 图案是左右对称, 反映在程序上就呈现出上下对称了. 当然也可以把三个语句写在一行里, 这样它也左右对称了:

star symmetry compare to code symmetry

程序是对客观世界的问题的一种映射, 因此程序的结构反映了问题的结构.

当然了, 如果你的程序写得很松散, 结构, 层次不清晰, 就不太容易看出这种对称来.

不知道是否有人意识到了, 在前面我有意无意地忽略了另一个对称问题. 这就是星号的对称, 在上图中只是把它当成对称轴看待.

撇开什么脱字符"^"或者空格, 其实单纯由星号构成的三角形也是对称的, 这是更主要的一个对称:

star symmetry

这种对称又来自哪里呢? 让我们深入到 getSecondPart 里面去:

private String getSecondPart(int lineNumber) {
    int count = getElementCountOfSecondPart(lineNumber);
    StringBuilder part = new StringBuilder();
    for (int i = 0; i < count; i++) {
        part.append(" ");
    }
    return part.toString();
}

很遗憾, 你看不到什么对称. 继续深入到 getElementCountOfSecondPart 去:

public int getElementCountOfSecondPart(int lineNumber) {
    return lineNumber * 2 + 1;
}

好了, 你已经到头了, 可好像还是找不到对称的影子呀? 前面说"程序的结构反映了问题的结构", 难道这个结论并不总是成立的?

有这么一个故事, 我估计很多人都听说过(文字摘自以下网址 http://www.niwota.com/submsg/5682754/):

有一位牧师在某个星期六的早晨正在为明天的讲道稿大伤脑筋. 他的太太外出买东西了, 外面正下着雨, 小儿子失去了户外活动的机会, 在屋里折腾. 牧师的思路一再被儿子打断.

牧师手边正好有本旧杂志, 为了把儿子从身边支开, 他撕下一张彩色世界地图, 再撕成碎片, 丢到客厅地板上对儿子说: "Johnny, 你把它拼成原样, 我就给你一个 Nickle(25 美分镍币). "

儿子有事可做, 又有报酬可得, 积极性很高, 立刻拼了起来. 牧师想, 这下子我可以安静一个上午, 构思自己的讲道稿了. 谁知道只隔了十分钟, 儿子就来敲书房的门了, 说已经拼好. 牧师不相信, 跑到客厅一看, 果然整幅地图完整无缺. 牧师又懊恼又惊奇地问儿子: "你怎么那么快就拼好了呢? "

儿子得意地说, 这再简单不过了, 这张地图的背后印着一幅人物肖像, 我想, 如果这个人拼对了, 世界地图也就拼对了.

牧师忍不住笑了起来, 很高兴地给了儿子一个镍币, 说, 你替我把明天讲道的题目也准备好了: 如果一个人是对的, 那么这个人的世界也是对的.

现在我们的程序能够输出一个对称的图案来, 肯定不是巧合, 而且我也可以肯定地告诉你, 这里面是有对称的. 你可以再仔细找找看, 如果你已经找到或者实在找不出来, 那么可以往下看了:

line num symmetry

现在看来是不是很明显呢? 也许你早也看出来了. 我们能得到有什么启示呢?

运用**直觉(Intuition)**去思考!

如果你的代码中怎么变换也找不出对称来, 当你的人都是错的时候, 你的世界还可能正确吗? 所以你甚至不用费心去上机验证了.

发明了**差分机(difference engine)的计算机先驱查尔斯·巴贝奇(Charles Babbage)**说:

我曾两次被(议员)问到, "巴贝奇先生, 请问假如您往机器里输入了错误的数据, 还会出来正确的答案吗? "我实在无法恰当地理解是怎样的逻辑混乱才会(使他们)提出这样的一个问题.

On two occasions I have been asked [by members of Parliament], ‘Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?’ I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

看得出来巴贝奇的评论还是很客气的, 他当时似乎在寻求议员拨款来支持他的差分机研究, 我估计他当时内心其实想说: “你丫脑子进水了, 问这种问题!”

巴贝奇谈的是往正确的机器里输入错误的数据的情况, 在这里, 如果连机器里面(即代码)都已经是错的, 那么即便给出了正确输入, 也不可能有正确的输出了.

在某些特殊情况下, 你也许会碰到"负负得正"的情况, 但这不过是巧合而已.

你可能遇到过这样的 bug:

你加了一个新特性, 系统出错了, 你找到一处 bug, 然后你很奇怪, 为何之前居然没问题? 而在你改正这个 bug 后, 之前的一些功能反而不正常了!你可能打死也想不到系统中还有另一个你没发现的 bug 在默默地抵消了它!

诚然, 这样的"负负得正"情况即便能工作也是非常脆弱的.

当事实与直觉不符时, 那么一定有什么地方出错了.

回到我们的问题, 再单独地拿输出空格部分来看:

public int getElementCountOfFirstPart(int lineCount, int lineNumber) {
    return lineCount - lineNumber - 1;
}

有对称吗? 它有两个不同变量, 单独的一份随便你怎么去变换, 你都找不到对称. 这也是为何你需要二份一左一右围绕在星号旁边才能形成对称.

关于直觉, 如果你还有兴趣, 可以见我之前写的另一篇文章(有些读者可能已经看过), 深入图解字符集与字符集编码–定长与变长, 也有谈到用天平之类的模型来直观地思考问题.

本质, 证明以及直觉

按前面那图:

line num symmetry part

如果有人试图从输出的 1, 3, 5, 7 的展开式的对称性来向你证明表达式"lineNumber*2+1"的对称性, 那么呢? 这不过是本末倒置. 正如前面所说, 你把表达式做个变换就可以看出表达式是对称的, 对称是该表达式的本质属性, 所以输出的1, 3, 5, 7 的对称性恰恰是表达式所决定的.

相信很多人都跟作者类似, 在大学的数学课上, 被那些形形色色的恐怖的证明弄得苦不堪言:

这里是在对""弹琴, 数学大""们请走开或请无视(要鄙视, 俺也认了), 这里不是在说你们!你们不会懂的!

如果你的老师只能通过证明来告诉你一件事情为什么是这样, 通常你还是很难明白它为什么是这样, 你甚至可以怀疑老师是否真的深刻理解了这件事情, 要么他就是不打算告诉你真正的原因:

好好反思一下你是否在什么事情上得罪了他? 你写作业是不是全靠"Ctrl+C, Ctrl+V"?

又或者他想让你自己去领悟:

真是用心良苦!你体会到了没有? 你领悟了吗? 要是没有请继续, 学费是不会退滴, 你别领悟到其它地方去了~

而如果你的老师可以通过直觉告诉你事情为什么是这样, 你也许就会说: "啊哈, 原来如此(Aha moment)!"从此你就记住了事情为什么是这样了, 那些冗长的证明你都可以丢到一边去了.

独立的数据模型(Isolated Data Model)

前面说到 getElementCountOfFirstPartgetElementCountOfSecondPart 两个方法达到了抽象的极致, 从而与具体的表现形式解耦. 在有了对称性之后, 我们甚至可以反转两个表现形式, 依然可以呈现出所谓的"三角形"出来:

star triangle compare to space triangle

对比两种情况, 不变的是三个部分中的那些数字:

star triangle number compare to space triangle number

所以这才是图案的"魂", 或者说是它的"意", 抽象出意, 我们就能"得意而忘形".

前面说过也许有一天, 客户的需求可能扩展到 web service 上, 对于一个较大的行数, 需要传输较大的数据, 但有了这种解耦, 我们可以把一个纯粹的数组传递过去:

[3][1][3]
[2][3][2]
[1][5][1]
[0][7][0]

另一方可以在收到这个数据模型后, 再把图形还原.

因为存在对称性, 第三列甚至也可以不传.

灵活性(Flexibility)与松耦合(Loose Couple)

而这个还原过程则可以很灵活地处理, 下图演示了在本地直接利用上述两方法获取图案模式并用 icon 图片展示的效果:

红薯三角形

代码如下(我对 swing 之类的编程也不是很熟, 随便在网上搜来的代码改了下):

public class PatternPic extends JPanel {

    private static final long serialVersionUID = 1L;
    
    private Image image;
    
    public PatternPic(Image image) {
        this.image = image;
    };
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int width = image.getWidth(this);
        int height = image.getHeight(this);
    
        int lineCount = 4;
        Pattern pattern = new Pattern();
    
        for (int lineNumber = 0, y = 0; lineNumber < lineCount; lineNumber++, y += height) {
            // 直接获取各部分的数目
            int elementCountOfFirst = pattern.getElementCountOfFirstPart(lineCount, lineNumber);
            int elementCountOfSecond = pattern.getElementCountOfSecondPart(lineNumber);
    
            for (int i = 0, x = elementCountOfFirst * width; i < elementCountOfSecond; i++, x += width) {
                g.drawImage(image, x, y, width, height, this);
            }
        }
    }
    
    public static void main(String[] args) throws IOException {
        Image image = ImageIO.read(PatternPic.class.getResource("/logo-git-oschina.png"));
        JFrame frame = new JFrame();
        frame.add(new PatternPic(image));
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

所以这样的抽象是有很大好处的, 我们说高层的一些抽象如果觉得啰嗦可以去掉一些(如上述就没作过多的抽象), 但这一层的抽象反而要保留. 我们把它作为公共的 API 提供出去, 调用者就不再受限于那些写死的"空格与星号", 这种灵活性与前面说到的可重用性及可扩展性都是紧密相关的.

MVC

MVC 中重要的一点即是强调模型(M, Model)与视图(V, View)的分离. 所谓的模型即是一种独立于视图的数据, 这与我们抽象出来的数据模型是很相似的.

真正的 MVC 与我们这里的例子间或许还有很大差异, 但在解耦合这一点, 两者是一致.

可以对比下最初的玩具式的代码, 那种情况就是一种 紧耦合(tight couple), 而这里则达到了 松耦合(loose couple) 的目的.

在当下, 常常一方面要处理传统的 web 界面, 另一方面又要处理移动端的界面, 抽取出纯粹的数据模型无疑将带给我们很大方便.

在下一篇将谈谈单元测试的问题.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/186582.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

动态规划学习——子序列问题

目录 ​编辑 一&#xff0c;最长定差子序列 1.题目 2&#xff0c;题目接口 3&#xff0c;解题思路及其代码 一&#xff0c;最长定差子序列 1.题目 给你一个整数数组 arr 和一个整数 difference&#xff0c;请你找出并返回 arr 中最长等差子序列的长度&#xff0c;该子序列…

《使用Python将Excel数据批量写入MongoDB数据库》

在数据分析及处理过程中&#xff0c;我们经常需要将数据写入数据库。而MongoDB作为一种NoSQL数据库&#xff0c;其具有强大的可扩展性、高性能以及支持复杂查询等特性&#xff0c;广泛用于大规模数据存储和分析。在这篇文章中&#xff0c;我们将使用Python编写一个将Excel数据批…

什么是强化学习

1 概况 1.1 定义 强化学习&#xff08;Reinforcement Learning, RL&#xff09;是机器学习的一个重要分支&#xff0c;与监督学习和无监督学习并列。它主要涉及智能体&#xff08;agent&#xff09;在环境中通过学习如何做出决策。与监督学习的主动指导和无监督学习的数据探索…

五、双向NAT

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 双向NAT1.1.基本原理1.2.NAT Inbound NAT Server1.3.域内NATNAT Server —————————————————————————————————————————————————— 双向NAT 经过前面介绍&#xff0c;…

NX二次开发UF_CURVE_ask_curve_struct 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_curve_struct Defined in: uf_curve.h int UF_CURVE_ask_curve_struct(tag_t curve_id, UF_CURVE_struct_p_t * curve_struct ) overview 概述 Gets the structure p…

post请求参数全大写后台接不到参数

post请求参数全大写后台接不到参数 开发过程中&#xff0c;我们一般都习惯用驼峰命名法&#xff0c;但是特殊情况要求请求参数全大写&#xff08;或者首字母大写&#xff09;&#xff0c;测试验证的时候发现&#xff0c;接收不到请求参数。 前端请求传递&#xff1a; 服务端接…

数字图像处理(实践篇)二 画出图像中目标的轮廓

目录 一 涉及的OpenCV函数 二 代码 三 效果图 一 涉及的OpenCV函数 contours, hierarchy cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]]) image&#xff1a;源图像。mode&#xff1a;轮廓的检索方式。cv2.RETR_EXTERNAL&#xff08;只检测…

实现简单的操作服务器和客户端(上)

一、说明 描述:本教程介绍如何使用 simple_action_server 库创建斐波那契动作服务器。此示例操作服务器生成斐波那契序列,目标是序列的顺序,反馈是计算的序列,结果是最终序列。 内容 创建操作消息编写一个简单的服务器 代码

第五届全国高校计算机能力挑战赛-程序设计挑战赛(C语言模拟题)

1、已有定义“int a[10]{1,2},i0;”&#xff0c;下面语句中与“ a[i]a[i1],i;”等价的是()。 A. a[i]a[i1]; B. a[i]a[i]; C. a[i]a[i1]; D. i,a[i-1]a[i]; 2、两次运行下面的程序&#xff0c;如果从键盘上分别输入6和4&#xff0c;则输出结果是(&#xff09;。 A. 7和5 …

常见树种(贵州省):015榧树、秋枫、滇合欢、锥栗、红豆树、刺槐、余甘子、黑荆、槐树、黄檀

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、榧树 …

tp8 使用rabbitMQ(3)发布/订阅

发布/订阅 当我们想把一个消息&#xff0c;发送给 多个消费者的时候&#xff0c;我们把这种模式叫做发布/订阅模式&#xff0c;比如我们做两个消费者&#xff0c;其中一个消费者把消息写入磁盘中&#xff0c;别一个消费者把消息结果输出到屏幕上&#xff0c;就要用到发布订阅模…

生物识别访问面临风险

安全公司 Blackwing Intelligence 发现了多个允许您绕过Windows Hello 身份验证的漏洞。 戴尔 Inspiron 灵越 15、联想 ThinkPad T14 和 Microsoft Surface Pro X笔记本电脑上会出现这种情况&#xff0c;原因是设备中集成了来自Goodix、Synaptics 和 ELAN的指纹传感器。 所有…

Windows核心编程 跨进程操作

目录 进程A拿到进程B句柄是否能用 句柄的权限 关于句柄表 跨进程使用句柄-继承 CreateProcess&#xff1a;bInheritHandles OpenProcess FindWinodw GetCurrentProcess 跨进程使用句柄-拷贝 跨进程操作内存 WriteProcessMemory VirtualProtectEx ReadProcessMemo…

情感对话机器人的任务体系

人类在处理对话中的情感时&#xff0c;需要先根据对话场景中的蛛丝马迹判断出对方的情感&#xff0c;继而根据对话的主题等信息思考自身用什么情感进行回复&#xff0c;最后结合推理出的情感形成恰当的回复。受人类处理情感对话的启发&#xff0c;情感对话机器人需要完成以下几…

npm pnpm yarn(包管理器)的安装及镜像切换

安装Node.js 要安装npm&#xff0c;你需要先安装Node.js。 从Node.js官方网站&#xff08;https://nodejs.org&#xff09;下载并安装Node.js。 根据你的需要选择相应的版本。 一路Next&#xff0c;直到Finish 打开CMD&#xff0c;输入命令来检查Node.js和npm是否成功安装 nod…

授时小课堂——北斗卫星信号和GPS卫星信号谁更强?

北斗卫星信号好还是GPS信号更胜一筹呢&#xff1f;下面小编带大家一起来比较一下看看吧。 1. 系统覆盖范围 北斗卫星导航系统是中国自主研发的授时定位系统&#xff0c;其覆盖范围包括全球各个地区。但在海外地区&#xff0c;主要还是东南亚、南亚、中亚等地区&#xff0c;北斗…

精通Nginx(18)-FastCGI/SCGI/uWSGI支持

最初用浏览器浏览的网页只能是静态html页面。随着社会发展,动态获取数据、操作数据需要变得日益强烈,CGI应运而生。CGI(Common Gateway Interface)公共网关接口,是外部扩展应用程序与静态Web服务器交互的一个标准接口。它可以使外部程序处理浏览器送来的表单数据并对此作出…

NX二次开发UF_CURVE_ask_curve_struct_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_curve_struct_data Defined in: uf_curve.h int UF_CURVE_ask_curve_struct_data(UF_CURVE_struct_p_t curve_struct, int * type, double * * curve_data ) overview…

数据结构与算法编程题20

统计二叉树的叶结点个数。 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1 typedef struct BiNode {ElemType data;BiNode* lchild, * rchild; }BiNode,*BiTree;bool Create_tree(BiTre…

JWT和Token之间的区别

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…