到底什么是框架?

作者:Intopass
链接:https://www.zhihu.com/question/25654738/answer/31302541
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


1 DRY 原则

首先从DRY原则开始说起Don’t Repeat Yourself,不要重复你的代码。DRY原则的重要性怎么提都不过分,很多人说编程是种机械性的工作,而有很多程序员也自嘲为码农,意为编程成了一种没有技术含量的体力性工作。如果不想沦为这个境界,首先需要的就是将DRY原则融入你的血液,在今后的编码工作中加以运用。


2 初级 DRY 应用


1)最初级的DRY:语法级别

System.out.println(1);
System.out.println(2);
……
System.out.println(10);

我想只要学过基础语法,都会采用下面的形式。

for (int i = 1; i <= 10; i++) {
    System.out.println(i);
}

如果发现有任何人采用上面一种形式的编码形式,那么不用怀疑,他对于编程绝对还没有入门。我们当然会选择省力的做法,这种做法不但省力,还会有利于我们后续修改或扩展这组代码,如:

for (int i = 1; i <= 10; i++) {
    System.out.println(i * 2 + 1);
}

我们进行这样的修改,只需要修改一处,而上面的形式却需要修改10处,当然会更麻烦且更容易出错,所以请记住能不重复就不重复。

2)进阶的DRY原则:方法级别当我们经常写一些重复性代码时,我们就要注意看能否将其抽取出来成为一个方法,如:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

让我们将其抽取到一个方法 threadSleep() 中,这样我们只需要调用 threadSleep() 就可以实现原来的功能,不但所需敲击的代码更少,而且代码看起来更加清楚明白。而为了增加这个方法的复用性,我们还可以将其中固定的数字抽取成为参数,如:

private static void threadSleep(int millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

这样我们就可以利用这个方法实现不同时间的sleep了。要注意提高代码的复用性也是实践DRY原则的一个重要方法,在后面我们也可以看到框架为了提高所谓的灵活性进行的一些设计,如在适当的位置增加扩展点。

3)继续进阶的DRY原则:类型级别现在我们看一个类

public class Person {
    private String name;
    private int age;
    // Setter & Getter ...
}

我们新建一些Person类实例,并进行一些操作:

Person person = new Person();
person.setName("jack");
person.setAge(18);
Person person2 = new Person();
person2.setName("rose");
person2.setAge(17);
.....
System.out.printf("Name: %s, Age:%d\n", person.getName(), person.getAge());
System.out.printf("Name: %s, Age:%d\n", person2.getName(), person2.getAge());
.....

观察这些代码,其实有很大的DRY改造空间,首先可以添加一个构造方法

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

其次,可以添加一个toString()方法

public String toString() {
    return String.format("Name: %s, Age: %d", name, age);
}

这样的话,上面的代码就可以改成下面的形式。

Person person = new Person("jack", 18);
Person person2 = new Person("rose", 17);
......
System.out.println(person.toString());
System.out.println(person2.toString());
......

4)继续继续进阶的DRY原则:多个类组合级别上面的代码我们其实还是有改善空间,就是利用容器类

List<Person> list = new ArrayList<>();
list.add(new Person("jack", 18));
list.add(new Person("rose", 17));
......
list.forEach(p -> System.out.println(p));

这里利用JDK8的Stream API以及Lambda表达式输出,其实可以进一步简化为

list.forEach(System.out::println);

这里我们可以看到,基本上我们写代码只写有变化的代码,而尽量不写机械性重复性的代码,其实后面我们就会知道,这就叫专注于业务逻辑,所谓业务逻辑就是你这个项目中,与别的项目都不一样的地方,必须由你亲自去编写实现的部分。

其实容器类很大程度上也是为了帮助我们编写代码而被设计出来的,首先让我们不必为每一个对象起名字(省去了person,person2,…等变量),然后又为批量操作提供了可能性。像是这样一系列有用的类组合起来可以称之为类库。常用的类库有Commons-Lang包等,为我们提供了一大批实用方法,我之所以提到类库,也是因为框架其实也是一种特殊的类库,但是却与一般的类库有着本质的不同。


3 高级的 DRY 应用


②设计模式,更高层级的DRY应用

上面我讲到了DRY原则的几个层次,一般情况下大家也早就这样使用了,属于入门之后很容易自己就想到得一些层次。但是设计模式不一样,设计模式是经过长时间编码之后,经过系统性的总结所提出的针对某一类问题的最佳解决方案,又称之为最佳实践。

而在小规模的编码工作中,其实并不需要什么设计模式,只有大型程序才有设计模式发挥的空间,所以我们需要借助一些特定领域有足够规模的问题来了解一下设计模式存在的必要性。

1)连接数据库,进行一些操作,并安全释放数据库连接。

public static boolean updatePassword(String username, String password, String newpassword) {
    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    boolean success = false;
    try {
        conn = beginTransaction();
        stmt = conn.prepareStatement("select id, password from user where username = ?");
        stmt.setString(1, username);
        rs = stmt.executeQuery();
        if (rs.next()) {
            if (rs.getString("password").equals(password)) {
                PreparedStatement stmt2 = null;
                try {
                    stmt2 = conn.prepareStatement("update user set password = ? where id = ?");
                    stmt2.setString(1, newpassword);
                    stmt2.setLong(2, rs.getLong("id"));
                    success = stmt2.executeUpdate() > 0;
                } finally {
                    safeClose(stmt2);
                }
            }
        }
        commitTransaction(conn);
        return success;
    } catch (SQLException e) {
        rollbackTransaction(conn);
        throw new RuntimeException(e);
    } finally {
        safeClose(rs);
        safeClose(stmt);
        safeClose(conn);
    }
}

上面是一个简单的数据库事务,虽然只有一个查询和一个更新,但是想要将其继续简化却并不容易,虽然其中有关于业务逻辑的部分只是少量几行代码,但是初始化,异常,提交,回滚操作让我们很难抽取出一个合适的方法来。虽然我们已经抽取出了 begin,commit,rollback,safeClose等方法,但是仍嫌繁琐。

我们发现之所以我们难以抽取方法,主要是因为流程,因为里面牵扯到流程控制,而流程控制一般是由我们程序员来控制的,所以也就必然需要我们手动编码来完成。难道真的就不能继续简化了吗?这就是需要设计模式的时候了。

2)应用设计模式「模板方法模式」

public static boolean updatePassword(String username, String password, String newpassword) {
    return connection(conn -> statement(conn, "select id, password from user where username = ?", stmt -> {
        stmt.setString(1, username);
        return resultSet(stmt, rs -> {
            if (rs.next()) {
                if (rs.getString("password").equals(password)) {
                    long id = rs.getLong("id");
                    return statement(conn, "update user set password = ? where id = ?", stmt2 -> {
                        stmt2.setString(1, newpassword);
                        stmt2.setLong(2, id);
                        return stmt2.executeUpdate() == 1;
                    });
                }
            }
            return false;
        });
    }));
}

可以看到,所有的conn,stmt,rs的开启和关闭,事务的提交和回滚都不用自己手动编写代码进行操作了,之所以可以达到这个效果,就是因为使用了模板方法设计模式,核心就是通过回调方法传递想对资源进行的操作,然后将控制权交给另一个方法,让这个方法掌握流程控制,然后适当的时候回调我们的代码(也就是我们自己写的业务逻辑相关的代码)。这是需要额外写的几个方法

public interface ConnectionCallback<T> {
    T doConnection(Connection conn) throws SQLException;
}
public interface StatementCallback<T> {
    T doStatement(PreparedStatement stmt) throws SQLException;
}
public interface ResultSetCallback<T> {
    T doResultSet(ResultSet rs) throws SQLException;
}
public static <T> T connection(ConnectionCallback<T> callback) {
    Connection conn = null;
    T result = null;
    try {
        conn = beginTransaction();
        result = callback.doConnection(conn);
        commitTransaction(conn);
    } catch (SQLException e) {
        rollbackTransaction(conn);
        throw new RuntimeException(e);
    } finally {
        safeClose(conn);
    }
    return result;
}
public static <T> T statement(Connection conn, String sql, StatementCallback<T> callback) throws SQLException {
    PreparedStatement stmt = null;
    T result = null;
    try {
        stmt = conn.prepareStatement(sql);
        result = callback.doStatement(stmt);
    } finally {
        safeClose(stmt);
    }
    return result;
}
public static <T> T resultSet(PreparedStatement stmt, ResultSetCallback<T> callback) throws SQLException {
    ResultSet rs = null;
    T result = null;
    try {
        rs = stmt.executeQuery();
        result = callback.doResultSet(rs);
    } finally {
        safeClose(rs);
    }
    return result;
}

你们可能会疑惑,这些代码加上我们写的业务逻辑的代码,比原来的代码还要长,有什么必要使用这个设计模式。这正是我前面已经指出的一个问题,那就是要你的程序规模足够大才有必要应用设计模式,试想如果你有上百个乃至上千个数据库操作方法需要写,那么是不是写这几个额外的方法,就不算什么了呢。其实这正是DRY原则在更高层次上的应用,即结合设计模式来达到更高层次的代码复用效果,进而应用DRY原则。而想要在这个层次继续向上攀升,那就必须是结合众多设计模式以及一些高层架构设计,能够帮助我们实现这一目的的就是框架。

3)框架,是设计模式的集大成者,是DRY原则的最高应用

先让我们来看一下,使用框架会是什么样的一种体验?这里以Hibernate + Spring声明式事务为例

@Transactional
public boolean updatePassword(String username, String password, String newpassword) {
    User user = (User) session().createQuery("from User where username = :username")
            .setString("username", username)
            .uniqueResult();
    if (user != null && user.getPassword().equals(password)) {
        user.setPassword(newpassword);
        return true;
    }
    return false;
}

可以发现令人惊讶的简洁,而且代码逻辑异常清晰,完全不需要考虑conn,stmt,rs等资源的释放,以及事务的提交和回滚,但是这些事情其实框架已经默默的帮我们做到了。这才叫真正的专注于业务逻辑,尽最大可能的只写与业务逻辑有关的代码。当然这些框架的效果虽然神奇,其实只要细细探究其内部原理,是完全可以理解并掌握的。

<think> 嗯,用户这次询问的是Flink框架的基本介绍,结合对话历史来看,用户之前问过镜舟数据库,现在转向了流处理框架,可能是想构建实时数据处理的技术栈。需要特别注意用户提供的引用材料里包含四个关于Flink的段落,其中引用[1]和[2]尤其关键。 用户提供的材料已经概括了Flink的核心特性:分布式、高性能、高可用、精确一次处理。但需要把这些特性组织得更系统化。考虑到用户之前对镜舟数据库的技术细节感兴趣,应该同样强调Flink的架构实现原理。比如引用[3]提到的四个核心组件(JobManager/ResourceManager等)值得展开说明。 注意到用户要求用LaTeX格式表达数学公式。在描述窗口计算时可以加入类似$ window\_count = \sum_{i=t-5min}^{t} event\_i $的示例,但要注意所有行内公式必须用$...$包裹。独立公式如状态快照的Barrier传递机制需要用$$...$$单独呈现。 用户引用的材料提到Application模式(引用[4]),这个属于部署层面的知识,可以放在架构之后简要说明。不过用户当前问题聚焦在框架介绍,部署细节可能属于延伸内容。 上次回答镜舟数据库时采用了分模块呈现的方式(定位/特性/场景),这次可以延续类似结构: 1 框架定位(批流统一的本质) 2 核心特性(分布式/高性能等) 3 运行时架构(四个组件交互) 4 编程模型(DataStream/Table API等) 需要避免直接复制引用原文,而是整合[1][2]的关键描述。比如引用[2]强调的"批处理是流处理特例"这个观点非常重要,要用更通俗的语言解释。 最后生成问题时,要结合用户可能的后续需求:故障恢复机制(基于引用[2]的高可用描述)、精确一次保障(引用[1]的准确性)、以及和镜舟数据库的协同场景(延续历史对话)。 </think> Apache Flink 是一个**开源的分布式流处理框架**,其核心设计理念是实现高性能、高可用且精确的实时数据处理能力。以下是结构化解析: --- ### 一、核心定义与定位 1. **批流统一架构** Flink 将**批处理视为流处理的特殊场景**(有界数据流),通过同一套引擎处理实时流与批量数据,消除传统 Lambda 架构的复杂度[^1][^2]。 $$ \text{数据处理} = \begin{cases} \text{流处理} & \text{(无界数据)} \\ \text{批处理} & \text{(有界数据)} \end{cases} $$ 2. **核心特性** | **特性** | **技术实现** | |----------------|-----------------------------------------------------------------------------| | **分布式** | 任务自动切分到多节点并行执行,支持水平扩展 | | **高性能** | 基于内存计算 + 异步快照机制,延迟低至毫秒级 | | **高可用** | 故障自动恢复(Checkpoint/Savepoint)[^2] | | **精确性** | 提供 Exactly-Once 语义保障(如事务写入 Kafka)[^1] | --- ### 二、架构组成 Flink 运行时包含四大协同组件[^3]: ```mermaid graph TB Client -->|提交作业| Dispatcher Dispatcher -->|分配资源| JobManager JobManager -->|调度任务| TaskManager ResourceManager -->|资源管理| TaskManager ``` 1. **作业管理器 (JobManager)** 协调作业执行:解析 DAG、调度任务、故障恢复。 2. **资源管理器 (ResourceManager)** 管理 TaskManager 的资源槽(Slot),支持 YARN/K8s 集成。 3. **任务管理器 (TaskManager)** 执行数据计算任务,每个 TaskManager 包含多个 Slot。 4. **分发器 (Dispatcher)** 提供 REST 接口接收作业提交,启动作业执行[^3]。 > *部署模式*: > - **Application 模式**:每个应用独占集群,多任务共享资源(适合短周期作业)[^4] > - **Per-Job 模式**:每个作业独立集群(资源隔离性强) --- ### 三、关键能力 1. **状态管理** - 内置键值状态(Keyed State)、算子状态(Operator State) - 支持状态后端(State Backend):内存/RocksDB $$ \text{状态一致性} = \text{Checkpoint} + \text{Barrier对齐} $$ 2. **时间语义** | **时间类型** | **定义** | **典型场景** | |---------------|----------------------------------|-------------------------| | Event Time | 数据产生时刻(需水位线机制) | 乱序事件处理 | | Processing Time| Flink 处理时刻 | 低延迟监控 | 3. **窗口计算** ```python # 滚动窗口示例(每5分钟统计交易额) data_stream.key_by("user_id") \ .window(TumblingEventTimeWindows.of(Time.minutes(5))) \ .sum("amount") ``` --- ### 四、应用场景 1. **实时风控** - 银行交易流中检测异常模式(如 1 分钟内同一账户多次大额转账) 2. **实时数仓** - 将 Kafka 数据实时写入 Hudi 表,支持分钟级数据分析 3. **物联网预警** - 聚合传感器数据流,计算设备故障概率: $$ P_{fault} = \frac{\text{超阈值信号数}}{\text{总信号数}} \times 100\% $$ ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值