我从我们的谈话中学到了很多东西。
对话
几周前,午餐时间。
我的学弟告诉我:“我认为面向对象编程没有意义,它总是在复杂的事情上。”
“你为什么这么认为?” 我问。
伙计:“你看,即使是为了一些简单的任务,它也总是需要创建一堆类。 我不喜欢被迫创建一些类来完成事情的想法”。 “例如,你看到我们的密码验证器逻辑,一个函数就足够了,为什么还需要一个类?”
我:“同意。 你不必这样做。 但类层次结构就像抽象工具。 将其视为数据+函数“包”的抽象。 需要时使用它们,如果不需要,就留下它们。 继续你认为简单的方式”。
伙计:“是的,但我不明白为什么要发明这个类的想法,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
然后当我看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迷你语言,正在试图解决同样的问题——控制。
最后
算法 + 数据结构 = 程序
算法 = 逻辑 + 控制
所以,
程序=数据结构+逻辑+控制
谢谢阅读! 享受!
下一篇文章见。