面向对象机制之抽象类和接口联系与区别

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。 abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface的选择显得比较随意。


其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
一、理解抽象类
abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?


在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是 这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领 域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行 为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可 以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读 者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。

原文地址 :http://www.cnblogs.com/azai/archive/2009/11/10/1599584.html

----------------------------------------------update 2021年6月25日23:43:47

fileLoader 目前解析的数据格式有好几种?

试想,如果新增一种format格式的文件?如何利用抽象类或者接口带来的便利?

将文件解析的过程中需要特殊处理的类,也就是公有方法,例如多个format共用一套逻辑计算real_flg,这就可以放入抽象类. 

面试题: 简述接口和抽象类是 Java 面向对象设计的两个基础机制?

       接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到 API 定义和实现分离的目的。接口,不能实例化;不能包含任何非常量成员,任何 field 都是隐含着 public static final 的意义;同时,没有非静态方法实现,也就是说要么是抽象方法,要么是静态方法

Java 标准类库中,定义了非常多的接口,比如 java.util.List。抽象类是不能实例化的类,用 abstract 关键字修饰 class,其目的主要是代码重用。

除了不能实例化,形式上和一般的 Java 类并没有太大区别,可以有一个或者多个抽象方法,也可以没有抽象方法。

        抽象类大多用于抽取相关 Java 类的共用方法实现或者是共同成员变量,不可以被实例化,

通过继承的方式达到代码复用的目的。Java 标准库中,比如 collection 框架,很多通用部分就被抽取成为抽象类,例如 java.util.AbstractList。Java 类实现 interface 使用 implements 关键词,继承 abstract class 则是使用 extends关键词,我们可以参考 Java 标准库中的 ArrayList。

看一段代码:

这是个接口类,接口中所有的方法都是 public abstract修饰的,接口一般经历  定义接口 -> 实现接口 -> 使用接口。

这段代码为什么要接口呢?直接实现类不行吗?

当时是可以直接实现, 但是如果项目逻辑变化较大,在原来的实现类改来改去,会影响已经在调用实现的功能。破坏了既有功能, 如果当时设计的时候有接口,那么完全可以重新写一个接口 ,然后自己实现然后调用其,既不影响既有功能,也实现了新功能。这也是接口的用处:   利用接口可以达到 API 定义和实现分离的目的。

此外个人为人,代码应该使用接口,因为不同人对同一个接口的实现可能不一样, 但是殊途同归(只要按照接口定义返回需要的值即可)

public interface LoadDefectService {
  Map<String, String> loadMOpeDInfo(Jedis jedis);

  Map<String, List<MppLayoutPnlDPojo>> getProdLayoutPnlMap(Jedis jedis);

  HashMap<String, EdaMeasureDefecttypeD> getDefectCodeTypeList(Jedis jedis);
  String setProdId(String glassId, Timestamp minTime, Timestamp maxTime, String pId, Jedis jedis, String shopId);
  void processArrayDefect(FileGlassPojo glassPojo, Map<String, String> opeMap,
      Map<String, List<MppLayoutPnlDPojo>> layoutPnlMap, Jedis jedis);
  void processCfDefect(FileGlassPojo glassPojo, Map<String, String> opeMap,
      Map<String, List<MppLayoutPnlDPojo>> layoutPnlMap, Jedis jedis);
  void processOcDefect(FileGlassPojo glassPojo, Map<String, String> opeMap,
      Map<String, List<MppLayoutPnlDPojo>> layoutPnlMap, Jedis jedis);
  void processDefect(FileGlassPojo glassPojo, Map<String, String> opeMap,
      Map<String, List<MppLayoutPnlDPojo>> layoutPnlMap, Jedis jedis);

}

这是个抽象方法:

public abstract class AbstractFileParse {



    private static final Logger logger = LoggerFactory.getLogger(AbstractFileParse.class);

    public RabbitMQService getRabbitMQService() {
        return SpringContext.getBean(RabbitMQService.class);
    }

    public String calcJudgeCntReturnRealFlg(Jedis jedis, String formatType, String opeId, String glassId, String fileJgeCnt, String evtTimestamp,
                                            long fileMotifyTime, String shopId, int glassDefectCnt) {
        /**
         * 四种情况: 1.首次记录
         *          2.ProcEndTimestamp时间大于历史记录
         *         3.ProcEndTimestamp时间相等,且文件修改时间大于/等于历史记录,且jgeCnt不一致/一致。[含重跑逻辑]
         *        4.其他情况则为 N
         */
        String key = String.format("JUDGE_CNT:%s:%s", opeId, glassId);
        logger.info("judge:opeId:" + opeId + "glassId:" + glassId +"jgeCnt:" + fileJgeCnt);
        String evtTimestamp2 = string2TimeStamp(evtTimestamp);
        String redisValue = jedis.get(key);

        if (redisValue == null || "".equals(redisValue)){
            jedis.set(key, fileJgeCnt + "#" + evtTimestamp2 + "#" + String.valueOf(fileMotifyTime) + "#" + glassDefectCnt);
            // 设置过期时间为6个月
            jedis.expire(key, 15552000);
            return "Y";
        } else if( redisValue.indexOf("#") > -1 ){
            String redisJgeCnt = redisValue.split("#")[0];
            String redisEvtTime = redisValue.split("#")[1];
            long redisMotifyTime = Long.parseLong(redisValue.split("#")[2]);
            boolean sendUpdateDefectFlag = true;
            if (redisValue.split("#").length == 4) {
                int lastGlassDefectCnt = Integer.parseInt(redisValue.split("#")[3]);
                if (lastGlassDefectCnt == 0) {
                    sendUpdateDefectFlag = false;
                    logger.info("update0 glassId:{} opeId:{}", glassId, opeId);
                }
            }
            String redisEvtTimeStr = redisEvtTime.replace("-", "").replace(" ", "").replace(":", "");
            long intRedisEvtTime = Long.parseLong(redisEvtTimeStr);
            long intFileEvtTime = Long.parseLong(evtTimestamp);

            if (intRedisEvtTime < intFileEvtTime){
                updateRealFlgProc(jedis, formatType, opeId, glassId, redisJgeCnt, redisEvtTime, shopId, sendUpdateDefectFlag);
                jedis.set(key, fileJgeCnt + "#" + evtTimestamp2 + "#" + String.valueOf(fileMotifyTime) + "#" + glassDefectCnt);
                // 设置过期时间为6个月
                jedis.expire(key, 15552000);
                return "Y";
            }else if ((intRedisEvtTime == intFileEvtTime) && (redisMotifyTime <= fileMotifyTime)){
                if (!fileJgeCnt.equals(redisJgeCnt)){
                    updateRealFlgProc(jedis, formatType, opeId, glassId, redisJgeCnt, redisEvtTime, shopId, sendUpdateDefectFlag);
                }
                jedis.set(key, fileJgeCnt + "#" + evtTimestamp2 + "#" + String.valueOf(fileMotifyTime) + "#" + glassDefectCnt);
                // 设置过期时间为6个月
                jedis.expire(key, 15552000);
                return "Y";
            }
        }
        return "N";
    }

    protected void updateRealFlgProc(Jedis jedis, String formatType, String opeId, String glassId, String redisJgeCnt, String redisEvtTime, String shopId, boolean sendUpdateDefectFlag) {

        String fabId = opeId.substring(0, 1);

        //获取对应的三个表名称
        Map<String, Class> tableNameMap = StringUtils.getTableName(formatType,fabId);

        doQueryThenSendToRabbitMQ(jedis, formatType, opeId, glassId, redisJgeCnt, fabId, tableNameMap, KEY_GLASS, redisEvtTime, shopId);

        doQueryThenSendToRabbitMQ(jedis, formatType, opeId, glassId, redisJgeCnt, fabId, tableNameMap, KEY_PANEL, redisEvtTime, shopId);

        if (sendUpdateDefectFlag) {
            doQueryThenSendToRabbitMQ(jedis, formatType, opeId, glassId, redisJgeCnt, fabId, tableNameMap, KEY_DEFECT, redisEvtTime, shopId);
        }


    }

    private void doQueryThenSendToRabbitMQ(Jedis jedis, String formatType, String opeId, String glassId, String redisJgeCnt, String fabId,
                                           Map<String, Class> tableNameMap, String key, String redisEvtTime, String shopId) {
        final Class glassClazz = tableNameMap.get(key);
        logger.info("judge thenSendMQ is " + glassClazz.getSimpleName());
        final RabbitMQService RabbitMQService = getRabbitMQService();
        final Table table = (Table) glassClazz.getDeclaredAnnotation(Table.class);
        final String tableName = table.name();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Timestamp nowTimestamp = Timestamp.valueOf(dateFormat.format(new Date()));
        String nowTimestampStr = nowTimestamp.toString();
        JSONObject update_columns = new JSONObject();
        update_columns.put("real_flg", "N");
        update_columns.put("etl_timestamp", nowTimestampStr);

        JSONObject query_conditions = new JSONObject();
        query_conditions.put("glass_id", glassId);
        query_conditions.put("ope_id", opeId);
        query_conditions.put("jge_cnt", redisJgeCnt);
        query_conditions.put("real_flg", "Y");
//        方便过期数据
        query_conditions.put("etl_timestamp", nowTimestampStr);
        if ("TEG".equals(formatType)){
            query_conditions.put("proc_end", redisEvtTime);
        }else {
            query_conditions.put("evt_timestamp", redisEvtTime);
        }

        JSONObject updateJsonObj = new JSONObject();
        updateJsonObj.put("update_flg", "true");
        updateJsonObj.put("ope_tbl_name", tableName);
        updateJsonObj.put("query_conditions", query_conditions);
        updateJsonObj.put("update_columns", update_columns);

        logger.info("updateJsonObj:" + updateJsonObj.toJSONString());

        String routeKey = null;
//        Boolean flag = opeId.startsWith("C") && (opeId.endsWith("90") || opeId.endsWith("9I") || opeId.endsWith("63"));
        if (OPE_FORMAT_TEG.equals(formatType)){
            if(KEY_GLASS.equals(key)) {routeKey = "TEG_MEASURE";}
            if(KEY_PANEL.equals(key)) {
                routeKey = "TEG_STATISTIC";
            }
            if(KEY_DEFECT.equals(key)) {
                routeKey = "TEG_STATISTIC2";
            }
        }else if(fabId.equals("A")){
            if(KEY_GLASS.equals(key)) {routeKey = "A_GLASS";}
            if(KEY_PANEL.equals(key)) {routeKey = "A_PANEL";}
            if(KEY_DEFECT.equals(key)) {routeKey = "A_DEFECT";}
        }else if(fabId.equals("C")){
            if(KEY_GLASS.equals(key)) {routeKey = "F_GLASS";}
            if(KEY_PANEL.equals(key)) {routeKey = "F_PANEL";}
            if(KEY_DEFECT.equals(key)) {routeKey = "F_DEFECT";}
        }else{
            if(KEY_GLASS.equals(key)) {
                routeKey = "C_GLASS";
            }
            if(KEY_PANEL.equals(key)) {
                routeKey = "C_PANEL";
            }
            if(KEY_DEFECT.equals(key)) {
                routeKey = "C_DEFECT";
            }
        }
        if (opeId.equals("C7710") && routeKey.equals("F_DEFECT")) {
            return;
        }
        RabbitMQService.sendMessage(routeKey, updateJsonObj.toJSONString(), glassId, opeId);
    }



    private String string2TimeStamp(String time){
        if(!org.springframework.util.StringUtils.isEmpty(time)){
            return time.substring(0,4) + "-" + time.substring(4,6) + "-" + time.substring(6,8) + " " + time.substring(8,10) + ":" + time.substring(10,12) + ":" + time.substring(12,14);
        }else{
            return null;
        }
    }

    public boolean isRepetitionFile(Jedis jedis, FileGlassPojo glassInfo, String urlString, String defectFileName, String redisIndexFileName, RedisKeyService redisKeyService){
        final RabbitMQService RabbitMQService = getRabbitMQService();
        Set<String> LineUrlList = jedis.zrange(redisKeyService.getCurrentKey("LINE_URL:" + redisIndexFileName), 0, -1);
        for (String url:LineUrlList){
            if (url.split("#")[0].equals(urlString.split("#")[0])) {
                SystemAlarm systemAlarm = new SystemAlarm("system_alarm", UUID.randomUUID().toString(),"重复的文件","重复的文件,放弃解析", Timestamp.valueOf(LocalDateTime.now()),
                        defectFileName.substring(6).trim(),redisIndexFileName, glassInfo.getOpeId(), glassInfo.getEqptId(), glassInfo.getGlassId(),null);
                RabbitMQService.sendMessage("SYSTEM_ALARM", JSON.toJSONString(systemAlarm));
                logger.error("TDDO");
                return true;
            }
        }
        return false;
    }

}

几个设计原则问题?

  • 单一职责(Single Responsibility),类或者对象最好是只有单一职责,在程序设计中如果发现某个类承担着多种义务,可以考虑进行拆分。
  • 开关原则(Open-Close, Open for extension, close for modification),设计要对扩展开放,对修改关闭。换句话说,程序设计应保证平滑的扩展性,尽量避免因为新增同类功能而修改已有实现,这样可以少产出些回归(regression)问题。
  • 里氏替换(Liskov Substitution),这是面向对象的基本要素之一,进行继承关系抽象时,凡是可以用父类或者基类的地方,都可以用子类替换。
  • 接口分离(Interface Segregation),我们在进行类和接口设计时,如果在一个接口里定义了太多方法,其子类很可能面临两难,就是只有部分方法对它是有意义的,这就破坏了程序的内聚性。

对于这种情况,可以通过拆分成功能单一的多个接口,将行为进行解耦。在未来维护中,如果某个接口设计有变,不会对使用其他接口的子类构成影响。

开关原则实例:

public class VIPCenter {
void serviceVIP(T extend User user>) {
if (user instanceof SlumDogVIP) {
//
穷
X VIP
,
活动抢的那种
// do somthing
} else if(user instanceof RealVIP) {
// do somthing
}
// .
.
.
}

这段代码的一个问题是,业务逻辑集中在一起,当出现新的用户类型时,比如,大数据发现了我们是肥羊,需要去收获一下, 这就需要直接去修改服务方法代码实现,这可能会意外影响不相关的某个用户类型逻辑。

利用开关原则,我们可以尝试改造为下面的代码:

public class VIPCenter {
private Map<User.TYPE, ServiceProvider> providers;
void serviceVIP(T extend User user
)
{
providers.get(user.getType()).service(user);
}
}
interface ServiceProvider{
void service(T extend User user) ;
}
class SlumDogVIPServiceProvider implements ServiceProvider{
void service(T extend User user){
// do somthing
}
}
class RealVIPServiceProvider implements ServiceProvider{
void service(T extend User user) {
// do something
}
}

上面的示例,将不同对象分类的服务方法进行抽象,把业务逻辑的紧耦合关系拆开,实现代码的隔离保证了方便的扩展。

接口抽象类区别 抽象方法是必须实现的方法。就象动物都要呼吸。但是鱼用鳃呼吸,猪用肺呼吸。 动物类要有呼吸方法。怎么呼吸就是子类的事了。 现在有很多讨论和建议提倡用interface代替abstract类,两者从理论上可以做一般性的混用,但是在实际应用中,他们还是有一定区别的。抽象类一般作为公共的父类为子类的扩展提供基础,这里的扩展包括了属性上和行为上的。而接口一般来说不考虑属性,只考虑方法,使得子类可以自由的填补或者扩展接口所定义的方法,就像JAVA王子所说的事件中的适配器就是一个很好的应用。 用一个简单的例子,比如说一个教师,我们把它作为一个抽象类,有自己的属性,比如说年龄,教育程度,教师编号等等,而教师也是分很多种类的,我们就可以继承教师类而扩展特有的种类属性,而普遍属性已经直接继承了下来。 而接口呢~还是拿教师做例子,教师的行为很多,除了和普通人相同的以外,还有职业相关的行为,比如改考卷,讲课等等,我们把这些行为定义成无body的方法,作为一个集合,它是一个interface。而教师张三李四的各自行为特点又有不同,那么他们就可以扩展自己的行为body。从这点意义上来说,interface偏重于行为。 总之,在许多情况下,接口确实可以代替抽象类,如果你不需要刻意表达属性上的继承的话。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值