Java设计模式之——桥接模式

本文介绍了桥接模式的概念、原理及应用场景,通过实例演示了如何利用桥接模式将抽象与实现分离,实现系统的灵活扩展。

桥接模式的简单介绍

桥接模式也称为桥梁模式,是结构型设计模式之一。在现实生活中大家都知道“桥梁”是连接河道两岸的主要交通枢纽,简而言之其作用就是连接河的两边,而我们的桥接模式与现实中的情况很相似,也会承担这连接“两边”的作用,那么具体是哪两边呢?这里先不着急,我们先来看看该模式的定义。

桥接模式的定义

将抽象部分与实现部分分离,使它们都可以独立地进行变化。

桥接模式的使用场景

从模式的定义中我们大致可以了解到,这里“桥梁”的作用其实就是连接“抽象部分” 与“实现部分”,但是事实上,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以通过桥接模式使它们在抽象层建立一个关联关系。

桥接模式的 UML 类图

这里写图片描述

角色介绍:

  • Abstraction:抽象部分,该类保持一个对实现部分对象的引用,抽象部分中的方法需要调用实现部分的对象来实现,该类一般为抽象类。
  • RefinedAbstraction:优化的抽象部分,抽象部分的具体实现,该类一般是对抽象部分的方法进行完善和扩展。
  • Implementor:实现部分,可以为接口或抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分这些基本操作的业务方法。
  • ConcreteImplementorA、ConcreteImplementorB:实现部分的具体实现,完善实现部分中方法定义的具体逻辑。

桥接模式实战

现实生活中有很多桥接模式应用的影子,比如开关与具体的电器,开关的类型有多种,而电器也是各式各样,这两者是独立变化的且又有耦合。还有程序员天天面对的显示屏,对于显示屏来说它的尺寸与生产商之间也是一种二维关系,具体的尺寸与具体的厂商独立变化。而更贴近生活的例子就是我们在喝咖啡时,大家知道去咖啡馆喝咖啡一般分为 4 种。大杯加糖、大杯不加糖、小杯加糖、小杯不加糖,对于一杯咖啡来说这 4 种实质上就两种变化,一是大杯小杯,二是加糖不加糖,不管怎样,我们先来定义一个咖啡类。

public abstract class Coffee {
    protected CoffeeAdditives impl;
    public Coffee(CoffeeAdditives impl){
        this.impl = impl;
    }

    /**
     * 咖啡具体是什么样子有子类决定
     */

    public abstract void makeCoffee();
}

Coffee 类中保持了对 CoffeeAddITIves 的引用,以便调用具体的实现。同样地,咖啡还分大杯小杯,定义两个子类继承于 Coffee。

public class LargeCoffee extends Coffee {
    public LargeCoffee(CoffeeAdditives impl) {
        super(impl);
    }

    @Override
    public void makeCoffee() {
        Log.d("Coffee", "大杯的" + impl + "咖啡");
    }
}

public class SmallCoffee extends Coffee {
    public SmallCoffee(CoffeeAdditives impl) {
        super(impl);
    }

    @Override
    public void makeCoffee() {
        Log.d("Coffee", "小杯的" + impl + "咖啡");
    }
}

而对于加进咖啡中的糖,当然也可以选择不加,我们也用以抽象类定义:

public abstract class CoffeeAdditives {

    /**
     * 具体要往咖啡里添加什么也是有子类实现
     * @return  具体添加的东西
     */
    public abstract String addSomething();
}

注意,这里的 CoffeeAdditives 其实就对应于上面我们 UML 类图中的实现部分,而 Coffee 则对应于抽闲部分,其实模块定义中所谓的 “抽象” 与“实现”实质上对应的是两个独立变化的维度,因此,上面我们也曾说过,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。在本例中,我们的 Coffee 类虽然是一个抽象类,但它并非是所谓的“抽象部分”,而 CoffeeAdditives 类也并非一定就是“实现部分”,两者各自为一维度,独立变化,仅此而已,所谓的 “抽象与实现分离”更偏向于我们实际的开发,两者并不一定挂钩,这里其实就可以看到桥接模式的应用性其实很广泛,并不局限于程序设计。我们再来看 CoffeeAdditives 对应的两个子类:加糖与不加糖:

public class Sugar extends CoffeeAdditives {
    @Override
    public String addSomething() {
        return "加糖";
    }
}

public class Ordinary extends CoffeeAdditives {
    @Override
    public String addSomething() {
        return "原味";
    }
}

不加糖我们以原味表示,最后来看客户类,将两者进行整合:

public class Client {
    public static void main(){

        //原味
        CoffeeAdditives ordinary = new Ordinary();

        //糖类
        CoffeeAdditives sugar = new Sugar();

        //大杯咖啡 原味
        Coffee largeCoffeeOrdinary = new LargeCoffee(ordinary);
        largeCoffeeOrdinary.makeCoffee();

        //大杯咖啡 加糖
        Coffee largeCoffeeSugar = new LargeCoffee(sugar);
        largeCoffeeSugar.makeCoffee();

        //小杯咖啡 原味
        Coffee smallCoffeeOrdinary = new SmallCoffee(ordinary);
        smallCoffeeOrdinary.makeCoffee();

        //小杯咖啡 加糖
        Coffee smallCoffeeSugar = new SmallCoffee(sugar);
        smallCoffeeSugar.makeCoffee();

    }
}

代码逻辑很简单,不难理解,这里输出结果就不给出了。如果此时咖啡庁为了满足更多人的习惯,推出中杯的咖啡怎么办?对于本例来说,这种需求的变化其实就是 Coffee 来的变化,定义中杯类扩展 Coffee 类即可:

public class MiddleCoffee extends Coffee {
    public MiddleCoffee(CoffeeAdditives impl) {
        super(impl);
    }

    @Override
    public void makeCoffee() {
        Log.d("Coffee", "中杯的" + impl + "咖啡");
    }
}

对应的客户端做出相应的增加即可。

        //小杯咖啡 原味
        Coffee middleCoffeeOrdinary = new MiddleCoffee(ordinary);
        middleCoffeeOrdinary.makeCoffee();

        //小杯咖啡 加糖
        Coffee middleCoffeeSugar = new MiddleCoffee(sugar);
        middleCoffeeOrdinary.makeCoffee();

同样地,为了增加咖啡口味的种类,我们也可以让 CoffeeAdditives 类变化起来,增加更多的子类表示,诸如加奶、加蜂蜜、加盐等,具体的代码就不在给出了,相信大家已经很清楚。从本例中我们可以看到,不管是 Coffee 变化了还是 CoffeeAdditives 变化了,其相对于对方而言都是独立的没有什么过多的交集,两者之间唯一的联系就是 Coffee 中保持的对 CoffeeAdditives 的引用,此乃两者之纽带,这就是桥接模式。

总结

桥接模式可以应用到开发中,但是它应用得并不多,一个很重要的原因是对于抽象与实现分离的把握,是不是需要分离、如何分离?对于设计者来说要有一个恰到好处的分寸。不管怎么说,桥接模式的优点我们母庸置疑,分离抽象与实现、灵活的扩展以及对客户来说透明的实现等。但是使用桥接模式也有一个明显的缺点,上面我们也提到了,就是不容易设计,对开发者来说要有一定的经验要求。因此,对桥接模式应用来说,理解很简单,设计却不容易。

<think>根据用户需求,我们需要删除数据框中所有的值都不大于20的行。也就是说,对于每一行,如果该行的所有的值都小于等于20,那么我们就删除一行。 在R语言中,我们可以使用多种方法来实现这个功能。下面我将介绍几种常见的方法: ### 方法1: 使用`apply()`函数和逻辑判断 我们可以使用`apply()`函数按行(MARGIN=1)应用一个函数,该函数检查当前行是否所有元素都小于等于20。然后,我们可以用这个逻辑向量来子集数据框,保留至少有一个元素大于20的行。 ```r # 假设df是数据框 # 创建一个逻辑向量:对于每一行,如果所有值都<=20则为FALSE,否则为TRUE rows_to_keep <- apply(df, 1, function(row) any(row > 20)) # 然后使用这个逻辑向量来选取行 df_filtered <- df[rows_to_keep, ] ``` ### 方法2: 使用`rowSums()`函数 另一种方法是利用`rowSums()`计算每行大于20的值的个数,然后保留那些个数大于0的行。 ```r # 计算每行大于20的值的个数 count_greater_than_20 <- rowSums(df > 20) # 保留至少有一个值大于20的行 df_filtered <- df[count_greater_than_20 > 0, ] ``` ### 方法3: 使用dplyr包的`filter()`函数 如果你习惯使用tidyverse,可以使用dplyr包中的`filter()`函数结合`rowwise()`和`any()`,或者更高效地使用`if_any()`或`if_all()`(但注意这里我们需要的是任意一个大于20,所以用`if_any`)。 注意:`if_any`和`if_all`在dplyr 1.0.0以上版本引入,用于条件筛选。 ```r library(dplyr) # 使用if_any:选择至少有一个满足条件(大于20)的行 df_filtered <- df %>% filter(if_any(everything(), ~ . > 20)) # 或者使用rowwise(较慢,不推荐用于大数据) # df_filtered <- df %>% rowwise() %>% filter(any(c_across(everything()) > 20)) %>% ungroup() ``` ### 示例 假设我们有以下数据框: ```r df <- data.frame( a = c(15, 25, 5, 30), b = c(10, 18, 20, 22), c = c(18, 22, 15, 19) ) ``` 我们想删除所有都不大于20的行。那么: - 第一行:15,10,18 -> 都<=20,删除 - 第二行:25,18,22 -> 有大于20的,保留 - 第三行:5,20,15 -> 都<=20(注意20不大于20,是等于),删除 - 第四行:30,22,19 -> 有大于20的,保留 所以结果应该保留第二行和第四行。 ### 代码验证 用方法1: ```r rows_to_keep <- apply(df, 1, function(row) any(row > 20)) df_filtered <- df[rows_to_keep, ] # 输出 # a b c #2 25 18 22 #4 30 22 19 ``` ### 注意事项 1. 数据框中的非数值:如果数据框中有非数值(如字符、因子等),上述方法可能会出错,因为比较运算符(如`>`)只适用于数值。因此,在应用之前,请确保只对数值进行操作,或者先选择数值。例如: ```r # 假设df有混合,我们只选择数值 numeric_cols <- sapply(df, is.numeric) df_numeric <- df[, numeric_cols] # 然后对df_numeric应用上述方法,但注意这样会丢失非数值。所以更好的方法是先提取行索引,再对原数据框子集。 rows_to_keep <- apply(df[, numeric_cols], 1, function(row) any(row > 20)) df_filtered <- df[rows_to_keep, ] ``` 2. NA值处理:如果数据中有NA,那么比较结果可能为NA,这会影响结果。可以在比较时使用`na.rm=TRUE`参数(在`any`函数或`rowSums`中)。例如: ```r rows_to_keep <- apply(df, 1, function(row) any(row > 20, na.rm=TRUE)) # 或者 count_greater_than_20 <- rowSums(df > 20, na.rm=TRUE) ``` ### 总结 根据用户需求,推荐使用`apply`方法或`rowSums`方法,它们都是基础R的函数,不需要额外安装包。如果用户已经使用dplyr,那么使用`filter(if_any(...))`也很方便。 §§相关问题§§ 1. 如何删除数据框中所有的值都小于某个阈值的行? 2. R语言中如何根据条件删除数据框的行? 3. 在R语言中如何处理数据框中的NA值? 4. 如何选择数据框中的数值?</think>### 在R语言删除数据框中所有的值都不大于20的行 根据你的需求,我将详细介绍几种在R语言删除数据框中所有的值都不大于20的行的方法。核心思路是:**识别并保留至少有一个的值大于20的行**。 #### 方法1:使用`apply()`函数和`any()`逻辑判断(基础R) 这是最直接的方法,通过逐行检查是否至少有一个元素大于20: ```r # 创建逻辑向量:检查每行是否至少有一个值>20 keep_rows <- apply(your_dataframe, 1, function(row) any(row > 20)) # 删除不满足条件的行 filtered_df <- your_dataframe[keep_rows, ] ``` **说明**: - `apply(, 1, ...)` 表示按行操作 - `any(row > 20)` 检查当前行是否至少有一个值大于20 - `[keep_rows, ]` 保留逻辑值为TRUE的行 #### 方法2:使用`rowSums()`(高效处理大数据) 适用于大型数据集的高效方法: ```r # 计算每行中>20的值的数量 count_above_20 <- rowSums(your_dataframe > 20) # 保留至少有一个值>20的行 filtered_df <- your_dataframe[count_above_20 > 0, ] ``` **优势**:向量化操作,计算速度更快[^4] #### 方法3:使用dplyr包的`filter()`(tidyverse风格) 适合tidyverse用户: ```r library(dplyr) filtered_df <- your_dataframe %>% filter(if_any(everything(), ~ . > 20)) ``` **说明**: - `if_any(everything(), ...)` 检查所有 - `~ . > 20` 是简写函数,判断每个元素是否>20 #### 示例演示 假设原始数据框: ```r df <- data.frame( A = c(15, 25, 18, 10), B = c(20, 22, 16, 5), C = c(18, 30, 20, 15) ) ``` 应用方法1后: ``` A B C # 保留的行(至少有一个>20) 2 25 22 30 # 25>20, 22>20, 30>20 3 18 16 20 # 注意:20不大于20,但18>20? 错误→实际应删除此行 ``` **修正说明**:第三行所有值(18,16,20)均≤20,实际会被删除。正确结果只保留第2行。 #### 注意事项 1. **NA值处理**:默认情况下,`NA > 20`会返回`NA`,可能导致整行被误删。解决方案: ```r # 添加na.rm=TRUE keep_rows <- apply(your_dataframe, 1, function(row) any(row > 20, na.rm = TRUE)) ``` 2. **非数值处理**:如果数据框包含非数值,需先选择数值: ```r numeric_cols <- sapply(your_dataframe, is.numeric) filtered_df <- your_dataframe[rowSums(your_dataframe[, numeric_cols] > 20) > 0, ] ``` 3. **等号边界**:条件`>20`是严格大于,等于20的值不会触发保留条件(如示例第3行)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值