【OO第三次课下讨论】农场主的饲料分配问题
需求分析与项目设计
本思考题的设计需求是力图找到一个简单且可行的饲料分配方案,由于不涉及到饲料价格或者是营养均衡之类的优化问题,因此在假设总的饲料量必能满足所有动物的热量需求的前提下,我们只需要采用贪心策略就可以找到一个算法上的可行解。
当然,这里我们关注的不只是在算法思路上的尽可能简单,由于是OO,所以我们更需要将整个过程进行抽象和封装,从而使得该方案可以在顶层模块的架构上也要尽可能的简单清晰。而为了做到这一点,本节课上我们所学习到的知识(也就是继承与实现,多态与归一化等)可以说是必不可少的。
首先,我们可以看到,这个问题的核心就是要处理动物与饲料之间的一个映射关系,也就是某个动物分别需要哪几种饲料各多少,某种饲料分别投喂多少给哪几种动物这样。那么很显然,我们需要建立动物(Animal)和饲料(Fodder)这两个“家族”,至于它们都有哪些具体的成员以及成员之间有着怎样的关系(继承or实现)则取决于进一步对其属性和方法的分析。
对于动物而言,我们需要知道这个动物可以吃哪些饲料,以及它每天需要的总热量,这些都是属性,所以我们需要将Animal定义为一个父类而非接口;同理,对于饲料,我们也需要知道饲料的总重量以及每单位饲料所产生的相应热量,因此Fodder也必须是一个父类。1
明确属性只是第一步,接下来我们得知道对这些属性我们要执行什么操作,也就是方法。而对于方法的实现,首先应该明确的是我们站在谁的立场上去思考问题,如果我们站在饲料的立场上,那么我们关心的就是这个饲料应该喂给哪些动物,分别喂了多少等等;而如果我们站在动物的立场上,那么我们关心的就是这个动物吃了哪些饲料,都吃了多少。相信同学们看到这已经意识到了,没错,就本题而言,出题人已经提前帮我们明确了我们的立场——我们知道的是“每一种动物能吃哪些饲料有明确规定”,而非“每一种饲料能给哪些动物吃有明确规定”。
既然如此,我们就可以给动物一个基本方法(Key Method):喂食(feed)。这个方法的大致内容就是:如果这个动物所需的总热量还没有得到满足,就从它能吃的饲料里任选一种,尽最大可能地满足这个动物的热量需求。
其实这个方法同时也是我们算法的核心,比如如果一次喂食没有喂完怎么办?是优先把当前这个动物喂到饱,还是尽量给大家都均衡一些?不过由于这个跟我们本节课探讨的主题没什么关系,所以我们就不用管它了。这里笔者就采用了一种最简单的方法,也就是每次只选一种饲料,能给这只喂多少就喂多少,不够后面再说。
而围绕着这个核心方法,其他的方法也就自然而然地明确了,包括更新动物的可食用饲料列表,获取特定饲料的单位卡路里,获取特定饲料的当前剩余量,更新特定饲料的当前剩余量等等。最后我们得到的Animal与Fodder类的属性与方法如下图所示:
这里的FeedInfo是一个投喂信息类,由(Animal, Fodder, amount)三元组构成,代表一次喂食的基本信息。其具体代码见后文。
这样一来,我们在main方法中就只需要利用一个简单的循环就可以获得我们想要的饲料分配方案。为了便于演示,我设计了Cow和Pig两种动物,以及Water, Grass, Corn三种饲料。它们的基本信息如下所示:
Animal | Fodders | Calorie |
---|---|---|
Cow | Water, Grass | 500 |
Pig | Water, Corn | 300 |
Fodder | CaloriePerKG | Amount(可在初始化时设定) |
---|---|---|
Water | 20 | 20 |
Grass | 50 | 5 |
Corn | 80 | 3 |
main方法的代码如下:
public static void main(String[] args) {
//初始化基本信息
ArrayList<Animal> animals = new ArrayList<>(); Water water = new Water(20);
Grass grass = new Grass(5);
Corn corn = new Corn(3); Cow cow = new Cow();
cow.addFodder(grass);
cow.addFodder(water);
animals.add(cow); Pig pig = new Pig();
pig.addFodder(corn);
pig.addFodder(water);
animals.add(pig); ArrayList<FeedInfo> feedInfos = new ArrayList<>();
//制定饲料分配方案
while (true) {
boolean allFull = true;
for (Animal animal : animals) {
FeedInfo feedInfo = animal.feed();
if (feedInfo != null) {
feedInfos.add(feedInfo);
allFull = false;
}
}
if (allFull) {
break;
}
}
//输出最终信息
for (FeedInfo feedInfo : feedInfos) {
System.out.println(feedInfo.toString());
} }
整个项目的UML图如下:
最终程序运行输出的结果如下:
至此,整个饲料分配问题基本得到了较为完善的解决。
总结与归纳
那么,问题来了:在整个方案的设计与实现中,究竟哪里运用到了归一化和多态呢?
答案就在之前提到的FeedInfo类中:
public class FeedInfo {
private Animal animal;//投喂的动物
private Fodder fodder;//投喂的饲料
private double amount;//一次投喂的饲料量 public FeedInfo(Animal animal, Fodder fodder, double amount) {
this.animal = animal;
this.fodder = fodder;
this.amount = amount;
} @Override
public String toString() {
return animal.toString() + " " + fodder.toString() + " " + amount;
}
}
可以看到FeedInfo的toString()方法是直接调用了animal和fodder的toString()方法,但事实上,不同的具体的动物类和饲料类都对这个方法进行了重写,从而使得最终输出的结果里体现的是各自子类的toString(),而不是Animal的。
可能有的同学看到这会觉得有些失望:为啥到头来多态就用在了这么一个“不起眼”的地方呢?其实,这恰恰是因为作为父类的Animal和Fodder把子类的大部分方法和所有属性都已经实现了,子类也就没有必要再去修改啦(包括我们的feed()方法)。当然如果你把最后的输出结果放在feed()里面,那么子类就必须对它进行重写了,不过这样反而有些冗长而有损简洁美,所以我就没有这么做。2
额外的细节——
1.浅拷贝与深拷贝
如果有同学仔细地看了我的main函数的话,可能还会发现另一个问题:明明所有的饲料都是在main方法里定义的,动物是怎么在自己的feed()方法里对饲料的量进行修改的呢?其实这就涉及到面向对象里面另一个很重要的问题:关于深浅拷贝的使用与注意事项。简单来说,这里虽然所有的Fodder都是在main()函数里定义的,但是它们的引用却被传给了相应的动物。不仅如此,以water为例,cow和pig饲料列表里的water其实指向的是同一个water(类似c语言的指针),这样我们就可以很方便地在cow和pig各自的feed()方法里对water的量进行增减了。关于深浅拷贝具体的介绍还请感兴趣的同学自己到网上查一下,这里就不展开了。但是必须要强调的是,这个东西是把双刃剑,用的好可以让代码变得简洁,但要是用不好,很可能会出现一些不可描述的错误,还请各位务必小心哦~
2.生产者-消费者模式
另外有一点不得不说,就是这个农场模型很像并发里经典的生产者--消费者这样的关系——饲料是生产者,动物是消费者。不过区别在于,这里我们不是多线程并发实现的,而是所有的动物排好队一个一个来喂食的。
所以,现在我们不妨做一个更大胆的假设:如果有一天,农场变成了奥威尔笔下的《动物庄园》,所有的饲料都在一个仓库里,动物们自己过来拿自己想吃的吃,那么他们会打起来吗?如果采用我们现在的方法,又会发生什么有趣的事情呢?
【OO第三次课下讨论】农场主的饲料分配问题的更多相关文章
- 【OO课下讨论】bug中的“二八定律”
bug中的"二八定律" 本文主要为讨论2020/3/17下午OO课讨论的第三个思考题设立 有一个经典的经验性原则,叫帕累托原则,也称为二八定律.这个原则在经济.社会和科技等多个领域 ...
- OO前三次作业简单总结
随着几周的进行,OO课堂已经经历过三次课下作业.在这三次作业中,我被扣了一些分数,也发现了自己几次作业中一些存在的共同的问题. 首先以第三次作业为例分析,我程序的类图如下 一共九个类,其中Als_sc ...
- OO第三单元作业(JML)总结
OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...
- 渡过OO的死劫,了解规格的意义——OO第三次博客总结
当熬过了一次次黑暗,迎接我们的却是被扣的惨不忍睹的JSF ┭┮﹏┭┮ 一.总结调研 规格的历史 传统科学的特点是发现世界,而软件的特点是构造世界.软件的最底层就是0,1,两个离散的值.程序设计语言的三 ...
- OO第三次博客作业——规格
OO第三次博客作业——规格 一.调研结果: 规格的历史: 引自博文链接:http://blog.sina.com.cn/s/blog_473d5bba010001x9.html 传统科学的特点是发现世 ...
- OO第三单元作业总结
OO第三单元作业总结--JML 第三单元的主题是JML规格的学习,其中的三次作业也是围绕JML规格的实现所展开的(虽然感觉作业中最难的还是如何正确适用数据结构以及如何正确地对于时间复杂度进行优化). ...
- 【OO学习】OO第三单元作业总结
[OO学习]OO第三单元作业总结 第三单元,我们学习了JML语言,用来进行形式化设计.本单元包括三次作业,通过给定的JML来实行了一个对路径的管理系统,最后完成了一个地铁系统,来管理不同的线路,求得关 ...
- OO第三单元个人总结
OO第三单元个人总结 JML理论与基础与应用工具链 JML是什么? Java建模语言(JML)是一种行为接口规范语言,可用于指定Java模块的行为 .它结合了Eiffel的契约设计方法 和Larch ...
- 2020 OO 第三单元总结 JML语言
title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...
随机推荐
- cas sso单点登录 登录过程和登出过程原理说明
CAS大体原理我就不说了,网上一大把,不过具体交互流程没说清楚,所以有这篇文章,如果有错误,请多多指教 登录过程 用户第一次访问一个CAS 服务的客户web 应用时(访问URL :http://192 ...
- FaceIDViewer.rar
FaceIDViewer用于查看Office工具栏控件的内置图标编号. 压缩包中包含4个文件,打开任何一个都可以进行查询.其中带有V2字样的文件,已经排除掉了空白图标. Excel版动态图: Word ...
- Django学习之模板层
三板斧 render,HttpResponse,redirectrender返回一个HTML页面,并且还能够给该页面传数据render内部原理: from django.template import ...
- [LC] 234. Palindrome Linked List
Given a singly linked list, determine if it is a palindrome. Example 1: Input: 1->2 Output: false ...
- [LC] 5. Longest Palindromic Substring
Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...
- Office 365 的安装方法
一.在线安装 进入网址 https://www.office.com/ 使用office账号登陆 1.点击右上角安装office应用,选择第二项 其他安装选项 2.选择安装语言 点击高级,选择安装版本 ...
- Derby数据库的使用
一. Derby数据库平台的搭建 ● JDK 1.6版本及之后的版本为Java平台提供了一个数据库管理系统,简称Derby数据库. ● 连接Derby数据库需要有关的类,这些类以jar文件的形 ...
- Nginx 原理和架构
Nginx 是一个免费的,开源的,高性能的 HTTP 服务器和反向代理,以及 IMAP / POP3 代理服务器.Nginx 以其高性能,稳定性,丰富的功能,简单的配置和低资源消耗而闻名Nginx 里 ...
- CDC与HDC的区别以及相互转换
CDC是MFC的DC的一个类 HDC是DC的句柄,API中的一个类似指针的数据类型. MFC类的前缀都是C开头的 H开头的大多数是句柄 这是为了助记,是编程读\写代码的好的习惯. CDC中所 ...
- centos7 开机/etc/rc.local 不执行的问题(转载)
最近发现centos7 的/etc/rc.local不会开机执行,于是认真看了下/etc/rc.local文件内容的就发现了问题的原因了 #!/bin/bash # THIS FILE IS ADDE ...