Java基本功练习三(方法的抽象、逐步求精)

本文通过一个编程实例,详细讲解了在Java编程中如何运用方法的抽象和逐步求精思想来设计程序。从理解方法的组成部分到将大问题分解成小问题,再到画结构图辅助思考,最后通过自顶向下和自底向上的方法实现程序。这种方法不仅使程序易于读懂,调试,还提高了代码的可重用性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       首先啰嗦两句,在其他语言中方法称为过程(无返回值)或函数(有返回值)。Java中main方法是由Java虚拟机调用的,其方法头永远都是public static void main(String[]args)。另外,理解相关概念:方法头、方法体、修饰符、方法名、方法签名、参数列表、形式参数。

       方法的抽象和逐步求精:方法抽象是指将方法的使用和它的实现分离。逐步求精就是分治的思想,将大问题分解成子问题,子问题有分解为更小、更容易处理的问题。讲文字太过理论枯燥,下面以一道编程题目展开阐述。

       示例:假设编写一个程序,显示给定的年月和日历。程序提示用户输入年份和月份,然后显示该月的整个日历,运行效果如右图所示:

       你会立即开始编写代码吗?一个好的程序员应该学会程序设计前期的自顶向下的思维培养,应该先用方法抽象把细节与设计分离,只有在最后才实现这些细节。

       如上例子,可以先拆分为两个子问题:读入用户输入打印该年、月的日历。分解之后首先考虑的问题是还能分解成更小更简单的问题吗,而不是考虑用什么方法来读入和打印整个日历。可以画一个结构图来帮助思考。

       打印给定月份的日历问题可以分解成两个子问题:打印日历的标题(由年月、虚线、每周七天的星期名称)和日历的主体(第一天是周几、该月有几天、如何控制天数循环显示)。

       再继续往下分解,怎样知道某月第一天是周几呢?1)可以用最经典的泽勒一致性公式去求解(该方法在Java基本功练习二中介绍了,可以去参看);2)还有种办法是假如知道某一年,如1800年1月1日是星期三,然后计算1800年1月1日和日历月份之间相差的总天数,在用这个总天数+“3”对7求余运算(本例采用第2中方法)。

       如何知道总天数呢?需要知道该年是否是闰年以及每个月的天数。循环显示就最基本了,用计数器对7求余运算即可。另外:不得不考虑的是显示怎么对齐以保持美观的问题。思路分析的结构图如下图所示:


       到这里这个程序的整体的设计思路才算理清楚了,那么接下来就是如何实现各个子问题了。一般来说,每个子问题都对应实现中的一个方法,即使某些子问题太简单,以至于不需要方法来实现。决定哪些模块用方法实现,哪些模块要结合在另一个方法中,是基于整个程序易于读懂的原则的。例如本例中的年月的输入在main方法中就可以实现了。

       为了设计的流畅,建议采用自顶向下的方法来实现每个方法的框架,而用待完善的方法体来代替具体的方法体,先把框架搭建起来,后续在用自底向上的方法来丰富每个待完善的方法体,这样整个设计的思路和准确度都会提高不少。下面是自顶向下的设计框架源代码:

package PrintCalendar;

import java.util.Scanner;
public class PrintCalendar {
	public static void main(String[] args) {
		//自顶向下的框架设计
		Scanner input = new Scanner(System.in);
		System.out.print("Enter full year (e.g 2014): ");
		int year = input.nextInt();
		System.out.print("Enter month as number between 1 and 12: ");
		int month = input.nextInt();
		printMonth(year,month);
		}
		
		//打印月份
		public static void printMonth(int year, int month){
			System.out.print(month+" "+year);
		}
		//打印月份的标题
		public static void printMonthTile(int year,int month){
			
		}
		//打印月份的主体
		public static void printMonthBody(int year,int month){
			
		}
		//得到月份对应的英文字符串
		public static String getMonthName(int month){
			return "January";
		}
		//得到某个月第一天的星期数
		public static int getStartDay(int year,int month){
			return 10000;
		}
		//得到某个月的天数
		public static int getNumberOfDaysInMonth(int year,int month){
			return 31;
		}
		//判断某年是否是闰年
		public static boolean isLeapYear(int year){
			return true;
		}
}

       然后编译检测是否有错误,运行结果如右图所示:

       可以看出并没有编译运行等错误,证明主体框架的编写是对的(这样后期出错就排除了框架出错的可能,缩小了排错范围,提高效率)。现在就运用自底向上的方法进行设计细节的实现了。下面是设计好的源代码:

package PrintCalendar;

import java.util.Scanner;
public class PrintCalendar {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		System.out.print("Enter full year (e.g 2014): ");
		int year = input.nextInt();
		System.out.print("Enter month as number between 1 and 12 (e.g 12): ");
		int month = input.nextInt();
		//打印用户输入的年月份的日历
		printMonth(year,month);
	}

	//打印月份
	public static void printMonth(int year, int month){
		printMonthTile(year,month);
		printMonthBody(year,month);
	}
	//打印月份的标题
	public static void printMonthTile(int year,int month){
		System.out.println("\t"+getMonthName(month)+"  "+year);
		System.out.println("------------------------------");
		System.out.println(" Sun Mon Tue Wed Thu Fri Sat");
	}
	//打印月份的主体
	public static void printMonthBody(int year,int month){
		int startDay = getStartDay(year,month);
		int numberOfDaysInMonth = getNumberOfDaysInMonth(year, month);
		int i = 0;
		for(i = 0;i < startDay; i++)
			System.out.print("    ");
		for(i = 1;i <= numberOfDaysInMonth;i++){
			System.out.printf("%4d",i);
			if((i + startDay) % 7 == 0)
				System.out.println();
		}
		System.out.println();
	}
	//得到月份对应的英文字符串
	public static String getMonthName(int month){
		String monthName = "";
		switch(month){
		  case 1:monthName = "January";break;
		  case 2:monthName = "February";break;
		  case 3:monthName = "March";break;
		  case 4:monthName = "April";break;
		  case 5:monthName = "May";break;
		  case 6:monthName = "June";break;
		  case 7:monthName = "July";break;
		  case 8:monthName = "August";break;
		  case 9:monthName = "September";break;
		  case 10:monthName = "October";break;
		  case 11:monthName = "November";break;
		  case 12:monthName = "December";break;
		}
		return monthName;
	}
	//得到某个月第一天的星期数
	public static int getStartDay(int year,int month){
		final int START_DAY_FOR_JAN_1_1800 = 3;
		//得到从1800,1,1到year,month,1之间的天数
		int totalNumberOfDays = getTotalNumberOfDays(year,month);
		//返回year,month,1的开始天
		return (totalNumberOfDays + START_DAY_FOR_JAN_1_1800) % 7;
	}
	//得到年份之间的总天数
	public static int getTotalNumberOfDays(int year,int month){
		int total = 0;
		//得到从1800到1/1/year的总天数
		for(int i = 1800; i < year;i++){
			if(isLeapYear(i))
				total += 366;
			else
				total +=365;
		}
		//将一月到日历月的天数加到总天数中去
		for(int i = 1;i < month;i++)
			total +=getNumberOfDaysInMonth(year,i);
		return total;
	}
	//得到某个月的天数
	public static int getNumberOfDaysInMonth(int year,int month){
		if(month == 1 || month == 3 || month == 5 || month == 7 || 
				month == 8 || month == 10 || month == 12)
			return 31;
		if(month == 4 || month == 6 || month == 9 || month == 11)
			return 30;
		if(month == 2) 
			return isLeapYear(year) ? 29 : 28;
		return 0;//如果输入了无效月份返回0
	}
	//判断某年是否是闰年
	public static boolean isLeapYear(int year){
		return year % 400 == 0||(year % 4 == 0 && year % 100 != 0);
	}
}

       运行效果如右图所示:

       这样就完成了整个程序的开发流程,虽然开发步骤较多,从表面上似乎浪费了开发时间,但实际上更节省,并且调试更加容易,程序更加整齐、层次明显。程序的主体(通常就是指main方法)可以编写成由简单的方法构成的集合,更容易编写、调试、维护和修改了,并且最重要的是方法的可重用性非常高。为后续的团队开发和移植带来不可估量的好处。所以代码的规范化、方法的抽象、逐步求精有多重要,大家慢慢体会!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值