【设计模式活用】之代码重构之DAO扮演多个职责的重构案例

在本次关于GPS优化需求项目时,发现相关功能菜单查询页面共同一个DAO接口实现类,紧接着我看了一下listAll,pre,next,countAll方法业务代码高度相似,此已说明该DAO扮演着非单一原则,其承担着 系统多个功能菜单的查询功能。

1.背景介绍

  • 共同之处,就是查询共同表 ca_app_info,或者操作共同Hibernate对象VCaAppInfo。

  • 不同之处体现如下方面:1、包含HQL和SQL查询 。2、where条件不同(为了过滤数据以及根据页面表单条件动态判断拼加成一个StringBuffer对象(即SQL/HQL))。

1.1 业务分析

涉及查询的四个方法中(listAll,pre,next,countAll方法业务代码高度相似),代码冗余,不利于维护。

因此,借此机遇,对涉及本期需求GPS查询 四个页面进行重构,提炼出四个单据的实现类,统一继承抽象类,内部维护相关SQL或者HQL查询逻辑,调用方只需构造上线文,

产生不同的实现类即可,提高可扩展,可维护性。重构前代码,点击链接,查看方法:listAll,pre,next,countAll,你就晓得了。

源代码片段

@Override
	public List<VCaAppInfo> pre(VCaAppInfoId ci, int pn, int defaultPageSize) {
		// TODO Auto-generated method stub
		StringBuffer sb = new StringBuffer();
		sb.append("from VCaAppInfo v where 1=1");
		List<Object> listParams = new ArrayList<Object>();
		AbstractWhereExecutor<VCaAppInfoId> whereExecutor;
		WhereContext whereContext = new WhereContext(ci,sb,listParams,WhereContext.Strategy.HQL);
		String status = ci.getStatus();
		if (StringUtil.checkStatus(status)) {
			sb.append(" and v.id.status in (?)");
			listParams.add(status);
			sb.append(" and v.AUserId=? ");
			listParams.add(ci.getUser().getUserId());
 
			/*
			 * if("23".equals(status)){ //根据REFUSE_STATUS拒绝状态查询
			 * sb.append(" and v.refuseStatus in (?)");
			 * listParams.add(ci.getRefusestatus()); }else
			 * if("24".equals(status)){ sb.append(" and v.refuseStatus <> 3"); }
			 */
 
			if ("23".equals(status)) { // 根据REFUSE_STATUS拒绝状态查询
				sb.append(" and v.refuseStatus in (?)");
				listParams.add(ci.getRefusestatus());
			} else if ("24".equals(status) && !"3".equals(ci.getRefusestatus())) { // refusestatus
																					// =
																					// 0
																					// 1
																					// 终审
				sb.append(" and v.refuseStatus <> 3");
			} else if ("3".equals(ci.getRefusestatus())) { // 3 复核拒绝
				sb.append(" and v.refuseStatus = 3");
			}
 
		} else if ("80".equals(ci.getStatus())) { // 复审审批页面 显示自己审批的和信贷经理退回审批的
													// 150123
			sb.append(" and v.id.status in('23','30')");
			sb.append(" and v.AUserId=? ");
			listParams.add(ci.getUser().getUserId());
		} else if ("-27".equals(status)) { // 经销商上传GPS安装单 150302
			sb.append(" and (v.isGps ='0' or v.isGps ='2') and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null and v.id.status not in('11','12') ");
			// 经销商用户只能查看该门店单子 其他用户查看所有
			Set<SyUserDealer> ds = ci.getUser().getSyUserDealers();
			if (ds.size() != 0) {
				sb.append(" and v.id.dealerCode in(");
				Iterator<SyUserDealer> it = ci.getUser().getSyUserDealers()
						.iterator();
				while (it.hasNext()) {
					SyUserDealer d = it.next();
					int dealerCode = d.getId().getDealerCode();
					sb.append(dealerCode + " ,");
				}
				String allhql = sb.substring(0, sb.length() - 1);
				allhql += ") ";
				sb.setLength(0);// 清空 后加上
				sb.append(allhql);
			}
		} else if ("-28".equals(status)) { // GPS维护 已上传GPS安装
			whereExecutor = new InstalledGpsQueryExecutor(whereContext);
			whereExecutor.execute();
		} else if ("-29".equals(status)) { // GPS维护
			whereExecutor = new UnInstallGpsQueryExecutor(whereContext);
			whereExecutor.execute();
		} else if ("-30".equals(status)) { // 贷后资料维护
			sb.append(" and (v.ALOANARRIVE is null or v.ALOANARRIVE!='1') and v.id.status='32' ");
			if (ci.getIsOld() != null) {
				sb.append(" and v.isOld=? ");
				listParams.add(ci.getIsOld());
			}
			if (ci.getPostLoanStatus() != null) {// 新车贷后资料上传状态(包括花乡的二手车 即先放款后抵押)
				if ("0".equals(ci.getPostLoanStatus())) {// 未上传
					sb.append(" and (v.postLoanStatus is null or v.postLoanStatus='0' ) ");
				} else if ("1".equals(ci.getPostLoanStatus())) {// 已上传
					sb.append(" and v.postLoanStatus='1' ");
				} else if ("2".equals(ci.getPostLoanStatus())) {// 审批退回
					sb.append(" and v.postLoanStatus='2' ");
				} else if ("3".equals(ci.getPostLoanStatus())) {// 审批通过
					sb.append(" and v.postLoanStatus='3' ");
				}
			}
		} else if ("-31".equals(status)) { // 贷后资料维护回退
			sb.append(" and v.ALOANARRIVE='1' and v.id.status='32' ");
		} else if ("-32".equals(status)) { // 新车等待上传贷后资料
			sb.append(" and v.id.status='32' and ifnull(v.ALOANARRIVE,'')!='1' ");
			if (ci.getIsOld() != null) {
				sb.append(" and v.isOld=? ");
				listParams.add(ci.getIsOld());
			}
			if (ci.getPostLoanStatus() != null) {
				if ("0".equals(ci.getPostLoanStatus())) {// 未上传
					sb.append(" and (v.postLoanStatus is null or v.postLoanStatus='0') ");
				} else if ("1".equals(ci.getPostLoanStatus())) {// 已上传
					sb.append(" and v.postLoanStatus='1' ");
				} else if ("2".equals(ci.getPostLoanStatus())) {// 审批退回
					sb.append(" and v.postLoanStatus='2' ");
				} else if ("3".equals(ci.getPostLoanStatus())) {// 审批通过
					sb.append(" and v.postLoanStatus='3' ");
				}
			} else {
				sb.append(" and ifnull(v.postLoanStatus,'')!='1' and ifnull(v.postLoanStatus,'')!='3' ");// 所有,不显示已上传的申请单和审批通过的
			}
			// 经销商用户只能查看该门店单子 其他用户查看所有
			Set<SyUserDealer> ds = ci.getUser().getSyUserDealers();
			if (ds.size() != 0) {
				sb.append(" and v.id.dealerCode in(");
				Iterator<SyUserDealer> it = ci.getUser().getSyUserDealers()
						.iterator();
				while (it.hasNext()) {
					SyUserDealer d = it.next();
					int dealerCode = d.getId().getDealerCode();
					sb.append(dealerCode + " ,");
				}
				String allhql = sb.substring(0, sb.length() - 1);
				allhql += ") ";
				sb.setLength(0);// 清空 后加上
				sb.append(allhql);
			}
		} else if ("-33".equals(status)) {// 经销商GPS安装列表,TODO zhaozhaoxin
											// 20170411 add
			sb.append(" and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null and status in('15','16','17','18','19','25','26','27','28','29','32') ");
			// 经销商用户只能查看该门店单子 其他用户查看所有
			Set<SyUserDealer> ds = ci.getUser().getSyUserDealers();
			if (ds.size() != 0) {
				sb.append(" and v.id.dealerCode in(");
				Iterator<SyUserDealer> it = ci.getUser().getSyUserDealers()
						.iterator();
				while (it.hasNext()) {
					SyUserDealer d = it.next();
					int dealerCode = d.getId().getDealerCode();
					sb.append(dealerCode + " ,");
				}
				String allhql = sb.substring(0, sb.length() - 1);
				allhql += ") ";
				sb.setLength(0);// 清空 后加上
				sb.append(allhql);
			}
		} else {
			sb.append(" and v.id.status in (?)");
			listParams.add(status);
 
			// TODO zhaozhaoxin 20170308 add 进行分组查询
			if (isRoleQueryData(ci.getUser())) {
				getUserGroupIds(sb, ci.getUser(), status, true);
			} else {
				return null;
			}
		}
		if (ci.getFlowSeq() != null) {
			sb.append(" and v.flowSeq= ?");
			listParams.add(ci.getFlowSeq());
		}
		if (ci.getDealerName() != null) {
			sb.append(" and v.id.dealerName like ?");
			listParams.add("%" + ci.getDealerName() + "%");
		}
 
		if (ci.getAppCode() != null) {
			sb.append(" and v.id.appCode = ?");
			listParams.add(ci.getAppCode());
		}
 
		if (ci.getProppserName() != null) {
			sb.append(" and v.id.proppserName like ?");
			listParams.add("%" + ci.getProppserName() + "%");
		}
 
		if (ci.getIsLcv() != null) {
			sb.append(" and v.isLcv = ?");
			listParams.add(ci.getIsLcv());
		}
		if (ci.getAppTime() != null) {
			sb.append(" and date_format(v.id.appTime,'%Y%m%d') >= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getAppTime()));
		}
		if (ci.getAppTime2() != null) {
			sb.append(" and date_format(v.id.appTime,'%Y%m%d') <= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getAppTime2()));
		}
		if (ci.getUpdateTime1() != null) {
			sb.append(" and date_format(v.updateTime,'%Y%m%d') >= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getUpdateTime1()));
		}
		if (ci.getUpdateTime2() != null) {
			sb.append(" and date_format(v.updateTime,'%Y%m%d') <= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getUpdateTime2()));
		}
		if (ci.getLoanDate1() != null) {
			sb.append(" and date_format(v.loanTime,'%Y%m%d') >= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getLoanDate1()));
		}
		if (ci.getLoanDate2() != null) {
			sb.append(" and date_format(v.loanTime,'%Y%m%d') <= ?");
			listParams.add(DateUtil.getDateFormatE(ci.getLoanDate2()));
		}
		sb.append(" order by v.updateTime asc");// 从小到大排序 先提交的在前面
 
		List<VCaAppInfo> result = queryByHql(sb.toString(), pn,
				defaultPageSize, listParams.toArray());
		return result;
	}

1.2 思路分析

涉及四个功能菜单

  • 1、待上传GPS安装单
  • 2、GPS安装单列表
  • 3、GPS安装单未上传
  • 4、GPS安装单已上传

思路:分析如上四个菜单的查询特征,通过把相同和差异的抽象出来,即提取一个抽象类,通过模板方法,差异部分为抽象类子类,实现差异部分业务逻辑。

2、相关类图

2.1、executor类图

类图概览

2.2、context类图

context类图

3、相关源码

3.1、AbstractWhereExecutor

public abstract class AbstractWhereExecutor<T> implements Predicate<WhereContext> {
    /**
     * 上线文
     */
    protected WhereContext<T> context;
    /**
     * 参数DTO
     */
    protected T dto;
    /**
     * 查询条件
     */
    protected StringBuffer queryWhere;
 
    /**
     * 构造函数
     * @param context 上线文对象
     */
    public AbstractWhereExecutor(WhereContext<T> context) {
        this.context = context;
    }
 
    /**
     * 处理接口
     */
    public void execute() {
        this.execute((e)->{});
    }
 
    /**
     * 处理接口
     * @param after
     */
    public void execute(Consumer<WhereContext> after) {
        // 初始化
        this.prepare();
        // 检查参数
        this.check(context);
        // 执行鉴定
        if(this.test(context)){
            switch (context.getStrategy()){
                case SQL:
                    sql();
                    break;
                case HQL:
                    hql();
                    break;
                default:
                    break;
            }
        }
        // 后置处理
        after.accept(context);
    }
 
    /**
     * 参数检查
     * @param context
     */
    protected void check(WhereContext context){
        Assert.notNull(context.getDto(),"DTO对象为空");
        Assert.notNull(context.getStrategy(),"strategy为空");
    }
 
    /**
     * 初始化参数
     */
    protected void prepare(){
        this.dto = this.context.getDto();
        this.queryWhere = this.context.getQueryWhere();
    }
    /**
     * 由子类实现具体处理SQL条件
     */
    protected abstract void sql();
    /**
     * 由子类实现具体处理HQL条件
     */
    protected abstract void hql();
}

3.2、InstalledGpsQueryExecutor

public class InstalledGpsQueryExecutor extends AbstractWhereExecutor<VCaAppInfoId> {
    /**
     * 标示
     */
    protected String gpsFlow;
    /**
     * 审批人
     */
    protected Integer approvalUserId;
 
    /**
     * 构造函数
     *
     * @param context 上线文对象
     */
    public InstalledGpsQueryExecutor(WhereContext context) {
        super(context);
    }
 
    @Override
    protected void check(WhereContext context) {
        super.check(context);
        SyUser sessionUser = dto.getUser();
        Assert.notNull(sessionUser,"sessionUser为空");
    }
 
    @Override
    protected void prepare() {
        super.prepare();
        this.gpsFlow = dto.getGpsFlow();
        this.approvalUserId = dto.getUser().getUserId();
    }
 
    @Override
    protected void sql() {
        VCaAppInfoId dto = super.dto;
        StringBuffer where = super.queryWhere;
        if(this.isMine()){
            where.append(" and exists(select 1 from ca_gps_flow f where f.app_code = v.app_code and f.flow_step = 0 and f.approval_user_id = "+this.approvalUserId+") ");
        }
        where.append(" and v.status not in (11,13) and v.is_Gps ='2' and v.gps_Fee<>0 and v.gps_Fee !='' and v.gps_Fee is not null  and v.manage_Approval_Time is not null");
        if(!StringUtil.isBlank(dto.getProvince())){
            where.append(" and exists(select 1 from Sy_Dealer dr where dr.province = '"+dto.getProvince()+"' and dr.dealer_Code =  v.dealer_Code) ");
        }
        if (!StringUtil.isBlank(dto.getPostLoanStatus())) {
            //由于manual_audit_code多个时以";"分割,所以需要使用FIND_IN_SET函数处理 add by 石冬冬 on 2017/9/21
            where.append(" and v.app_code IN (SELECT app_code FROM Ca_Car_Gps WHERE del_status=1 and FIND_IN_SET ("+dto.getPostLoanStatus()+",replace(manual_audit_code,';',','))>0 ");
            where.append(" GROUP BY app_code) ");
        }
    }
 
    @Override
    protected void hql() {
        VCaAppInfoId dto = super.dto;
        StringBuffer where = super.queryWhere;
        if(this.isMine()){
            where.append(" and exists(select 1 from CaGpsFlow f where f.appCode = v.id.appCode and f.flowStep = 0 and f.approvalUserId = " + this.approvalUserId+")");
        }
        where.append(" and v.id.status not in (11,13) and v.isGps ='2' and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null");
        if(!StringUtil.isBlank(dto.getProvince())){
            where.append(" and exists(select 1 from SyDealer dr where dr.province = '"+dto.getProvince()+"' and dr.dealerCode =  v.id.dealerCode) ");
        }
        if (!StringUtil.isBlank(dto.getPostLoanStatus())) {
            //由于manual_audit_code多个时以";"分割,所以需要使用FIND_IN_SET函数处理 add by 石冬冬 on 2017/9/21
            where.append(" and v.id.appCode IN (SELECT appCode FROM CaCarGps WHERE delStatus=1 and find_in_set("+dto.getPostLoanStatus()+",replace(manualAuditCode,';',','))>0 ");
            where.append(" GROUP BY appCode) ");
        }
    }
 
    @Override
    public boolean test(WhereContext context) {
        VCaAppInfoId dto = (VCaAppInfoId)context.getDto();
        return "-28".equals(dto.getStatus());
    }
 
    private boolean isMine(){
        return "mine".equals(gpsFlow);
    }
}

3.3、TobeUploadGpsQueryExecutor

public class TobeUploadGpsQueryExecutor extends AbstractWhereExecutor<VCaAppInfoId> {
    /**
     * 构造函数
     *
     * @param context 上线文对象
     */
    public TobeUploadGpsQueryExecutor(WhereContext context) {
        super(context);
    }
 
    @Override
    protected void check(WhereContext context) {
        super.check(context);
        SyUser sessionUser = dto.getUser();
        Assert.notNull(sessionUser,"sessionUser为空");
    }
    @Override
    protected void sql() {
        StringBuffer where = super.queryWhere;
        SyUser sessionUser = dto.getUser();
        where.append(" and (v.is_Gps ='0' or v.is_Gps ='2') and v.gps_Fee<>0 and v.gps_Fee !='' and v.gps_Fee is not null  and v.manage_Approval_Time is not null and v.status not in('11','12') ");
        GpsUtil.appendDealerCodes(sessionUser,(list) -> {
            String condition = MessageFormat.format(" and v.dealer_Code in({0})", StringUtils.join(list,","));
            where.append(condition);
        });
    }
 
    @Override
    protected void hql() {
        StringBuffer where = super.queryWhere;
        SyUser sessionUser = dto.getUser();
        where.append(" and (v.isGps ='0' or v.isGps ='2') and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null and v.id.status not in('11','12') ");
        GpsUtil.appendDealerCodes(sessionUser,(list) -> {
            String condition = MessageFormat.format(" and v.id.dealerCode in({0})", StringUtils.join(list,","));
            where.append(condition);
        });
    }
 
    @Override
    public boolean test(WhereContext context) {
        VCaAppInfoId dto = (VCaAppInfoId)context.getDto();
        return "-27".equals(dto.getStatus());
    }
}

3.4、UnInstalledGpsQueryExecutor

/**
 * @description: 车贷审批管理》GPS安装单未上传
 * @Date : 上午10:17 2017/11/17
 * @Author : 石冬冬-Heil Hitler(dongdong.shi@mljr.com)
 */
public class UnInstalledGpsQueryExecutor extends AbstractWhereExecutor<VCaAppInfoId> {
    /**
     * 标示
     */
    protected String gpsFlow;
    /**
     * 审批人
     */
    protected Integer approvalUserId;
    /**
     * 构造函数
     *
     * @param context 上线文对象
     */
    public UnInstalledGpsQueryExecutor(WhereContext context) {
        super(context);
    }
 
    @Override
    protected void check(WhereContext context) {
        super.check(context);
        SyUser sessionUser = dto.getUser();
        Assert.notNull(sessionUser,"sessionUser为空");
    }
 
    @Override
    protected void prepare() {
        super.prepare();
        this.gpsFlow = dto.getGpsFlow();
        this.approvalUserId = dto.getUser().getUserId();
    }
 
    @Override
    protected void sql() {
        StringBuffer where = super.queryWhere;
        if(this.isMine()){
            where.append(" and exists(select 1 from ca_gps_flow f where f.app_code = v.app_code and f.flow_step = 0 and f.approval_user_id = "+this.approvalUserId+")");
        }
        where.append(" and v.status not in (11,13) and v.is_Gps ='0' and v.gps_Fee<>0 and v.gps_Fee !='' and v.gps_Fee is not null  and v.manage_Approval_Time is not null");
        if(!StringUtil.isBlank(dto.getProvince())){
            where.append(" and exists(select 1 from Sy_Dealer dr where dr.province = '"+dto.getProvince()+"' and dr.dealer_Code =  v.dealer_Code) ");
        }
    }
 
    @Override
    protected void hql() {
        VCaAppInfoId dto = super.dto;
        StringBuffer where = super.queryWhere;
        if(this.isMine()){
            where.append(" and exists(select 1 from CaGpsFlow f where f.appCode = v.id.appCode and f.flowStep = 0 and f.approvalUserId = "+this.approvalUserId+")");
        }
        where.append(" and v.id.status not in (11,13) and v.isGps ='0' and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null");
        if(!StringUtil.isBlank(dto.getProvince())){
            where.append(" and exists(select 1 from SyDealer dr where dr.province = '"+dto.getProvince()+"' and dr.dealerCode =  v.id.dealerCode) ");
        }
    }
 
    @Override
    public boolean test(WhereContext context) {
        VCaAppInfoId dto = (VCaAppInfoId)context.getDto();
        return "-29".equals(dto.getStatus());
    }
 
    private boolean isMine(){
        return "mine".equals(gpsFlow);
    }
}

3.5、UploadedGpsQueryExecutor

public class UploadedGpsQueryExecutor extends AbstractWhereExecutor<VCaAppInfoId> {
    private final String STATUS_SCOPE = "'15','16','17','18','19','25','26','27','28','29','32'";
    /**
     * 构造函数
     *
     * @param context 上线文对象
     */
    public UploadedGpsQueryExecutor(WhereContext context) {
        super(context);
    }
 
    @Override
    protected void check(WhereContext context) {
        super.check(context);
        SyUser sessionUser = dto.getUser();
        Assert.notNull(sessionUser,"sessionUser为空");
    }
 
    @Override
    protected void sql() {
        VCaAppInfoId dto = super.dto;
        StringBuffer where = super.queryWhere;
        SyUser sessionUser = dto.getUser();
        where.append(" and v.gps_Fee<>0 and v.gps_Fee !='' and v.gps_Fee is not null  and v.manage_Approval_Time is not null and status in("+STATUS_SCOPE+") ");
        GpsUtil.appendDealerCodes(sessionUser,(list) -> {
            String condition = MessageFormat.format(" and v.dealer_Code in({0})", StringUtils.join(list,","));
            where.append(condition);
        });
    }
 
    @Override
    protected void hql() {
        VCaAppInfoId dto = super.dto;
        StringBuffer where = super.queryWhere;
        SyUser sessionUser = dto.getUser();
        where.append(" and v.gpsFee<>0 and v.gpsFee !='' and v.gpsFee is not null and v.manageApprovalTime is not null and status in("+STATUS_SCOPE+") ");
        GpsUtil.appendDealerCodes(sessionUser,(list) -> {
            String condition = MessageFormat.format(" and v.id.dealerCode in({0})", StringUtils.join(list,","));
            where.append(condition);
        });
    }
 
    @Override
    public boolean test(WhereContext context) {
        VCaAppInfoId dto = (VCaAppInfoId)context.getDto();
        return "-33".equals(dto.getStatus());
    }
}

3.6、调用方

入口

4、归纳总结

如上,即使用了模板方法这个设计模式,模板方法也是最通常使用的,即把相同的逻辑骨架抽象处理,由子类实现各自的差异部门。

下面的是我的公众号二维码图片,欢迎关注,或公众号搜索【秋夜无霜】。
秋夜无霜

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
/* * 原始需求背景: * 网宿CDN要按月收取客户的服务费用,根据流量的大小、 * 服务的类型等,收取不同的费用,收费规则如下: * web应用:1000元/M * 流媒体应用:1000元/M*0.7 * 下载应用:1000元/M*0.5 * 月末打印报表时,要罗列每个用户每个频道的费用、客户总费用, * 还要打印该客户的重要性指数,重要性指数=网页流/100+下载流量/600; * * 需求变更场景: * 系统已经开发出来了,接下来,运维部门现在希望对系统做一点修改, * 首先,他们希望能够输出xml,这样可以被其它系统读取和处理,但是, * 这段代码根本不可能在输出xml的代码中复用report()的任何行为,唯一 * 可以做的就是重写一个xmlReport(),大量重复report()中的行为,当然, * 现在这个修改还不费劲,拷贝一份report()直接修改就是了。 * 不久,成本中心又要求修改计费规则,于是我们必须同时修改xmlReport() * 和report(),并确保其一致性,当后续还要修改的时候,复制-黏贴的问题就 * 浮现出来了,这造成了潜在的威胁。 * 再后来,客服部门希望修改服务类型和用户重要性指数的计算规则, * 但还没决定怎么改,他们设想了几种方案,这些方案会影响用户的计费规则, * 程序必须再次同时修改xmlReport()和report(),随着各种规则变得越来越复杂, * 适当的修改点越 来越难找,不犯错误的机会越来越少。 * 现在,我们运用所学的OO原则和方法开始进行改写吧。 */

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值