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