百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程文章 > 正文

初级开发人员告诉我:OO 设计模式太复杂而且没用

qiyuwang 2025-03-25 17:06 10 浏览 0 评论

我从我们的谈话中学到了很多东西。

对话

几周前,午餐时间。

我的学弟告诉我:“我认为面向对象编程没有意义,它总是在复杂的事情上。”

“你为什么这么认为?” 我问。

伙计:“你看,即使是为了一些简单的任务,它也总是需要创建一堆类。 我不喜欢被迫创建一些类来完成事情的想法”。 “例如,你看到我们的密码验证器逻辑,一个函数就足够了,为什么还需要一个类?”

我:“同意。 你不必这样做。 但类层次结构就像抽象工具。 将其视为数据+函数“包”的抽象。 需要时使用它们,如果不需要,就留下它们。 继续你认为简单的方式”。

伙计:“是的,但我不明白为什么要发明这个类的想法,OO 设计模式是做什么用的? 这么复杂,还有DI/IOC,好像没有必要”,“那么,什么是MVC模式? 我们正在使用的 Web 框架?”,“我喜欢函数式编程完成工作的方式,它非常简单且副作用更少”。

我:“你问的是不同的事情,给我一些时间来巩固答案,我保证会把链接发给你”。

为了把详细信息返回给我的兄弟。 我问自己第一个问题:“面向对象编程的核心思想是什么”。

面向对象编程

我记得一些著名的 OO 模式,如模板方法、工厂、单例、责任链和装饰器。

他们都在尝试做一些共同的事情——控制。

模板法。 主流程控制。 派生具体类中的“步骤”实现。

#include 
#include 
class View { // AbstractClass
public:
  // defines abstract primitive operations that concrete subclasses define to implement steps of an algorithm.
  virtual void doDisplay() {}
  // implements a template method defining the skeleton of an algorithm. The template method calls primitive operations as well as operations defined in AbstractClass or those of other objects.
  void display() {
    setFocus();
    doDisplay();
    resetFocus();
  }
  virtual ~View() = default;
private:
  void setFocus() {
    std::cout << "View::setFocus\\n";
  }
  void resetFocus() {
    std::cout << "View::resetFocus\\n";
  }
};
class MyView : public View { // ConcreteClass
  // implements the primitive operations to carry out subclass-specific steps of the algorithm.
  void doDisplay() override {
    // render the view's contents
    std::cout << "MyView::doDisplay\\n";
  }
};
int main() {
  // The smart pointers prevent memory leaks
  std::unique_ptr myview = std::make_unique();
  myview->display();
}
  • 工厂 控制对象的创建。
#include 
#include 
enum ProductId {MINE, YOURS};
// defines the interface of objects the factory method creates.
class Product {
public:
  virtual void print() = 0;
  virtual ~Product() = default;
};
// implements the Product interface.
class ConcreteProductMINE: public Product {
public:
  void print() {
    std::cout << "this=" << this << " print MINE\\n";
  }
};
// implements the Product interface.
class ConcreteProductYOURS: public Product {
public:
  void print() {
    std::cout << "this=" << this << " print YOURS\\n";
  }
};
// declares the factory method, which returns an object of type Product.
class Creator {
public:
  virtual std::unique_ptr create(ProductId id) {
    if (ProductId::MINE == id) return std::make_unique();
    if (ProductId::YOURS == id) return std::make_unique();
    // repeat for remaining products...
    return nullptr;
  }
  virtual ~Creator() = default;
};
int main() {
  // The unique_ptr prevent memory leaks.
  std::unique_ptr creator = std::make_unique();
  std::unique_ptr product = creator->create(ProductId::MINE);
  product->print();
  product = creator->create(ProductId::YOURS);
  product->print();
}
  • 单例 控制有多少个对象(仅允许 1 个)穿过系统。
#include 
class Singleton {
public:
  static Singleton& get() {
    static Singleton instance;
    return instance;
  }
  int getValue() {
    return value;
  }
  void setValue(int value_) {
    value = value_;
  }
private:
  Singleton() = default;
  ~Singleton() = default;
  int value;
};
int main() {
  Singleton::get().setValue(42);
  std::cout << value='<< Singleton::get().getValue() << '\\n';
}
  • 责任链 控制请求的处理或过滤方式(允许多个处理程序)
#include 
#include 
typedef int Topic;
constexpr Topic NO_HELP_TOPIC = -1;
// defines an interface for handling requests.
class HelpHandler { // Handler
public:
  HelpHandler(HelpHandler* h = nullptr, Topic t = NO_HELP_TOPIC)
    : successor(h), topic(t) {}
  virtual bool hasHelp() {
    return topic != NO_HELP_TOPIC;
  }
  virtual void setHandler(HelpHandler*, Topic) {}
  virtual void handleHelp() {
    std::cout << helphandler::handlehelp\\n optional implements the successor link. if successor successor->handleHelp();
    }
  }
  virtual ~HelpHandler() = default;
  HelpHandler(const HelpHandler&) = delete; // rule of three
  HelpHandler& operator=(const HelpHandler&) = delete;
private:
  HelpHandler* successor;
  Topic topic;
};
class Widget : public HelpHandler {
public:
  Widget(const Widget&) = delete; // rule of three
  Widget& operator=(const Widget&) = delete;
protected:
  Widget(Widget* w, Topic t = NO_HELP_TOPIC) 
    : HelpHandler(w, t), parent(nullptr) {
    parent = w;
  }
private:
  Widget* parent;
};
// handles requests it is responsible for.
class Button : public Widget { // ConcreteHandler
public:
  Button(std::shared_ptr h, Topic t = NO_HELP_TOPIC) : Widget(h.get(), t) {}
  virtual void handleHelp() {
    // if the ConcreteHandler can handle the request, it does so; otherwise it forwards the request to its successor.
    std::cout << "Button::handleHelp\\n";
    if (hasHelp()) {
      // handles requests it is responsible for.
    } else {      
      // can access its successor.
      HelpHandler::handleHelp();
    }
  }
};
class Dialog : public Widget { // ConcreteHandler
public:
  Dialog(std::shared_ptr h, Topic t = NO_HELP_TOPIC) : Widget(nullptr) {
    setHandler(h.get(), t);
  }
  virtual void handleHelp() {
    std::cout << "Dialog::handleHelp\\n";
    // Widget operations that Dialog overrides...
    if(hasHelp()) {
      // offer help on the dialog
    } else {
      HelpHandler::handleHelp();
    }
  }
};
class Application : public HelpHandler {
public:
  Application(Topic t) : HelpHandler(nullptr, t) {}
  virtual void handleHelp() {
    std::cout << "Application::handleHelp\\n";
    // show a list of help topics
  }
};
int main() {
  constexpr Topic PRINT_TOPIC = 1;
  constexpr Topic PAPER_ORIENTATION_TOPIC = 2;
  constexpr Topic APPLICATION_TOPIC = 3;
  // The smart pointers prevent memory leaks.
  std::shared_ptr application = std::make_shared(APPLICATION_TOPIC);
  std::shared_ptr dialog = std::make_shared(application, PRINT_TOPIC);
  std::shared_ptr

然后当我看IOC和DI时。 他们携手合作,也做同样的事情——控制:抽象应该依赖于哪个类?

OO的核心思想似乎就是控制。 我们使用接口或抽象类作为抽象规则或策略,然后使用类来实现不同的方式。 OO 的美妙之处在于控制(抽象)和业务逻辑(具体类)分离。

随着我越来越多的搜索,我发现罗伯特已经在这里发表了一篇论文。

那么接下来这个家伙的第二个问题,MVC 模式在 Web 框架中做什么?

MVC

当每个 HTTP 请求都经过平衡器然后路由到我们的应用程序时。 它需要一些一般处理。

比如我们需要解析 header 和 body,找出哪个函数或方法应该处理这个请求,并且数据是从查询字符串或表单正文或 JSON 正文或文件中获取的。

我们不想为每个新添加的类或函数一遍又一遍地执行此操作,这种处理可以通用化。

是的,它不一定是 MVC。

如果我们看看我们老式的 PHP、asp、jsp 或其他语言。 您可以从 PHP 中的 $server 或 asp 中的 Request[””] 获取标头值。 但还有一些工作需要推广:比如请求路由、过滤、视图模板引擎和模型反序列化。

这就是每个 MVC Web 框架可以给我们带来的东西。

因此,MVC 框架工作的答案是 — 带来更高级别的抽象来概括请求的处理。

是的,这又是关于控制。 一般而言,控制我们处理请求的方式。

泛型

这让我想起,当用 Java 和 C# 进行编码时,C++ 中的泛型类型或模板正在做完全相同的事情——抽象出类型处理,并使代码专注于业务逻辑本身。

让我们编写一个简单的函数来将数字加 1。

int inc(int n){
    return n+1
}

因此,如果我们想为 float 类型或 double 类型添加相同的函数,我们需要将上述函数重复 3 次。 如果我们想让它线程安全怎么办? 我们需要为以上三个函数添加锁或同步(或使用互斥锁)。 我们不需要下面的代码。

int inc(int n){
    lock (obj){
        return n+1;
    }
}
float inc(float n){
    lock (obj){
        return n+1;
    }
}
...

所以这里需要对类型泛化进行控制。 这就是泛型发挥作用的地方。

T inc(T n) {
    lock (obj){
        return n+1;
    }
}

因此,使用上述一种版本的代码就可以满足所有要求。

那么你是否也看到了同样的事情呢? 是的,控制。 控制(类型泛化)和业务逻辑(增加、类型安全)的分离。

我哥们喜欢FP。 我们也去那里吧。

函数式编程

让我们尝试通过讨论这三个著名的函数来触及 FP 的核心思想:map、reduce 和 filter。

因此,如果我们想要将字符串数组中的所有偶数相乘 ['1','2','3','4','5','6']。 这能做什么?

程序方式:

s = 0
for a in array:
    a = int(a)
    if a%2 == 0:
        s+=a
return s

函数式的方法:

reduce(lambda a,b: a*b, filter(lambda x:x%2==0, map(int, ['1','2','3','4','5','6']) ) )

正如你所看到的。

从 string 到 int 的类型转换由 map 函数处理

偶数由过滤函数处理

通过先乘后求和来减少函数

所有这些功能都是通过管道传输的

所以是的。 你明白了。 我们仍然看到同样的事情! 控制和业务逻辑。

函数(map、reduce、filter)就像控制(如何、数据转换流程)

lambda 是业务逻辑(什么,乘以偶数)

在 FP 中,控制侧重于“如何”,而业务逻辑则侧重于“做什么”。

看看优雅的管道! 是的,出于同样的原因,UNIX shell 也很漂亮。 还因为每个“管道”都同意仅处理文本(KISS)。

既然我们谈论了 shell,那么让我们稍微深入一下。

DSL(领域特定语言)

外壳是DSL。

它构建了一个层来允许开发人员(尤其是 DevOps 人员)有效地完成工作。 就像 SQL 如何帮助 DBA 人员一样,Shell 也做了类似的工作。

如果你看一下DSL,它是一个抽象层。 它可以是一种迷你语言,使我们的开发人员能够轻松地与 UNIX(Shell、AWK)或数据库(SQL)或“大数据系统”(HIVE)“对话”; 或一些语言解析器,如 JIRa (JQL) 和 Kibana(KQL); 有时它们是编程语言运行时(JVM、CLR)或解释器本身(Perl、Python); 它不一定是解析器或解释器,这里也有 OO 世界中的解释器模式。

所以这是一个想法。

DSL 是一个中间层,使人类和系统之间的“通信”变得容易,在系统级别或代码级别。

所以,再说一遍,作为抽象本身,DSL 是控制层,而用法是业务逻辑(例如 SQL 查询、shell、awk 代码逻辑)。

是的,分离控制和业务逻辑。 再次。


密码验证代码示例

程序方式:

def validate(pwd):
    if len(pwd) < 6:
        return False
    if not has_special_chars(pwd):
        return False
    if used_in_last_2_month(pwd):
        return False

面向对象:

interface IValidator{
    bool validate(string pwd);
}
class SpecialCharValidator:IValidator {}
class PwdHistoryValidator:IValidator {}
class PwdLengthValidator:IValidator {}
class StrongPasswordValidator{
     public (Ienumerate validators) {
         ...
     }
     public validate(string pwd){
         for (var v in validators){
             if (!v.validate(pwd)){
                 return false;
             }
          return true;
     }
}

DSL 方式:

spec {
'min_len': 8,
'used_pwd_max_days': 60,
'special_chars': '!@#$%^&*()'
}
class pwd_parser:
    def __init__(spec):
        load_parser(spec)
    ...
    def run() -> bool:

def validate(pwd) -> bool:
    return pwd_parser(spec).run()

如果我进行代码审查,我将批准该代码的所有 3 个版本。

因为这正是我们想要表达抽象的方式。 这就是如何分离控制和业务逻辑。

使用接口或抽象类作为控制和业务逻辑转到具体类。 使用面向对象编程。

通用函数(如 map、reduce、filter、decorator、walk、permutation、combination 等)或 DSL + 解析器作为控制和 lambda 定义业务逻辑。 去FP。

程序方式。 控制和逻辑混合在一起,也很好。 只要代码足够小。

没有所谓的完美方法来完成事情。 只要 :

我们关心我们正在编写的每一行代码

熟悉如何正确、干净地完成工作

可读且简单

做一个务实的程序员,足够好就够了。

致我的兄弟

伙计,我想你正在读这篇文章,正如我们商定的那样。 我可以发布我们的对话与其他人分享! 感谢您提出的好问题,我学到了很多。

结论

作为开发人员,我们在日常工作中将控制和业务逻辑分开。 我们的界限越清晰,我们得到的代码就越好。

所以OO模式,IoC和DI; MVC 框架; 映射减少和过滤功能; DSL迷你语言,正在试图解决同样的问题——控制。

最后

算法 + 数据结构 = 程序

算法 = 逻辑 + 控制

所以,

程序=数据结构+逻辑+控制

谢谢阅读! 享受!

下一篇文章见。

相关推荐

# 安装打开 ubuntu-22.04.3-LTS 报错 解决方案

#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...

利用阿里云镜像在ubuntu上安装Docker

简介:...

如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本

UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...

Ubuntu 16.10内部代号确认为Yakkety Yak

在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...

如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)

在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...

Win11学院:如何在Windows 11上使用WSL安装Ubuntu

IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...

如何查看Linux的IP地址(如何查看Linux的ip地址)

本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...

怎么看电脑系统?(怎么看电脑系统配置)

要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...

如何查询 Linux 内核版本?这些命令一定要会!

Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...

深度剖析:Linux下查看系统版本与CPU架构

在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...

504 错误代码解析与应对策略(504错误咋解决)

在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...

猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录

10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...

域名解析的原理是什么?域名解析的流程是怎样的?

域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...

Linux无法解析域名的解决办法(linux 不能解析域名)

如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。  此时,需要手动修改配置文件:  /etc/resolv.conf  将域名解析服务器手动添加到配置文件中  该文件是DNS域名解...

域名劫持是什么?(域名劫持是什么)

域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...

取消回复欢迎 发表评论: