The following code breaks the Law of Demeter:
public class Student extends Person {
private Grades grades;
public Student() {
}
/** Must never return null; throw an appropriately named exception, instead. */
private synchronized Grades getGrades() throws GradesException {
if( this.grades == null ) {
this.grades = createGrades();
}
return this.grades;
}
/** Create a new instance of grades for this student. */
protected Grades createGrades() throws GradesException {
// Reads the grades from the database, if needed.
//
return new Grades();
}
/** Answers if this student was graded by a teacher with the given name. */
public boolean isTeacher( int year, String name ) throws GradesException, TeacherException {
// The method only knows about Teacher instances.
//
return getTeacher( year ).nameEquals( name );
}
private Grades getGradesForYear( int year ) throws GradesException {
// The method only knows about Grades instances.
//
return getGrades().getForYear( year );
}
private Teacher getTeacher( int year ) throws GradesException, TeacherException {
// This method knows about Grades and Teacher instances. A mistake?
//
return getGradesForYear( year ).getTeacher();
}
}
public class Teacher extends Person {
public Teacher() {
}
/**
* This method will take into consideration first name,
* last name, middle initial, case sensitivity, and
* eventually it could answer true to wild cards and
* regular expressions.
*/
public boolean nameEquals( String name ) {
return getName().equalsIgnoreCase( name );
}
/** Never returns null. */
private synchronized String getName() {
if( this.name == null ) {
this.name == "";
}
return this.name;
}
}
Questions
How is the LoD broken?
Where is the code breaking the LoD?
How should the code be written to uphold the LoD?
I think that here are two problems:
Grades logic is too much mixed with Student. It should be done in Grades class
Teacher's logic is placed into Student.
Conclusion: Student knows too much about inner structure and logic of Teacher and Grades and that breaks LoD
Most problems such as this can be solved by revisiting your domain model.
It looks like the Student has way more responsibility than it should. It should have only one reason to change.
I would refactor this by adding a ReportCard object.
public class ReportCard
{
public Student Student...
public int Year...
public ReportCardItem[] ReportCardItems...
getGrades()...
createGrades()...
}
public class ReportCardItem
{
public Grade Grade...
public string Subject...
public Teacher Teacher...
}
Methods in class Student which break the Law of Demeter are
private Grades getGradesForYear( int year )
private Teacher getTeacher( int year )
because these expose domain objects Grades and Teacher to the application.
Assuming that you wish to continue to hide the Grades inside a Student and a Teacher inside Grades, one way to remedy this problem is to define proxy methods (also called delegate methods) in class Student that operate on the internal Grades and Teacher objects on behalf of the application, similar to method Student.isTeacher(int, String). This solution may lead to duplication of methods in Grades and Teacher in Student which is a disadvantage of a class design which respects the LofD.
A better solution would be to remove the Grades and Teacher from Student and put them all in another class, say Transcript:
class Transcript {
Student student;
Teacher teacher;
Grades grades;
Integer year;
}
Person.isTeacher "reaches through" according to the wikipedia article you mention.
I was surprised to find the list of grades a property of the student. Shouldn't that be something the school knows about and manages? I'd ask the school, which teacher graded a student in which year...
By having these two private functions breaks LoD.
private Grades getGradesForYear( int year )
private Teacher getTeacher( int year )
Students shouldn't need the logic to to perform such tasks.
The way I would redesign this is to separate data from logic. Student should purely be a data only. It should contain information about the student and student only. Therefore this does not include Grades as that concept requires others such as subject, and teacher.
The same goes for teacher. I would then create a place to store grade information and another place for subject information.
To perform similar tasks I would do this:
gradesDatabase.getGrade(subject, student);
subjectDatabase.getTeacher(subject, student);
Where subject is also a data only object.
Related
This is part of a lab I couldn't figure out... I can't figure out what is wrong in the addGrade method in the roster class, where I have to add a grade to a student, and if the student doesn't already exist, create a new student and then add the grade. Note that initially, this class didn't have the instance variable Student stu, which I added when trying to get things to work.
Student is provided with a constructor, student scores are saved in a linkedlist. I only put a part of the code here... it has methods to get student name, get score, add score, and get score average.
My code is no longer working after some edits... when it was partially working, it just overwrote the previous students with the latest one. Student a was added with grade 5, then student b with 7, then student a is added again with 10. This should have student a with 2 entries (5, 10) in the linkedlist. When I ran my code it only had student a with 10, but also didn't work completely.
public class Student {
private String name;
private List scores = new LinkedList<>();
public Student(String name)
{
this.name = name;
}
public void addGrade(int score)
{
scores.add(score);
}
public class Roster {
String name;
int score;
Student stu;
//Adds a grade to the end of a list of grades for the named student.
//Should work even if no student with this name has ever been seen before.
public void addGrade(String name, int score) {
Student temp = new Student(name);
stu.addGrade(score);
}
//Gets the specified grade from the named student's scores.
public int getGrade(String name, int index) {
int a = stu.getScore(index);
return a;
}
//Gets the average for the named student.
public double getAverage(String name) {
return stu.getAverage();
}
}
A roster is a list of students.
A student has a list of scores.
This is not all of the code you will need, just part of your Roster class and the addGrade() method there:
public class Roster {
List<Students> students = new LinkedList<Student>();
public void addGrade(String name, int score) {
// Student s = null;
// Search for existing student.
for (Student currentStu : students) {
if (currentStu.name.equals(name) {
s = currentStu;
}
}
if (s == null) {
//Student not in our roster. Add him.
s = new Student(name);
}
// Add the score to that student.
s.addGrade(score);
}
}
There's a lot of things going on with this code, but I'll give you some pointers on what could be going wrong. I strongly encourage you to reach out to your teacher, a tutor or a TA to close the loop on some things, but for the most part, here are some of the conceptual errors that I see.
A Student is the most basic piece of information. A Roster contains a collection of students. It is likely the case that you want a List<Student> in your Roster class. You don't require any other fields than that in Roster.
You should provide a way to add more scores to a specific student, but this particular aspect I'll leave for you to discuss with your teacher.
Your current linked list declaration of scores is untyped. This is frowned upon since you will generate unchecked warnings at compile time, and if you accidentally add a non-numeral into that linked list, you'll get an error at runtime when attempting to do math on it.
It's also interesting that you use a linked list instead of an array-backed list, since you intend to index into the list. For performance reasons, I would recommend using ArrayList.
With all of that said, you would want to type it as List<Integer> scores = new ArrayList<>().
You need a way to search for a student by name. This is a bit tricky since you're storing Student entries in there, but it can be done. I'll describe a very rudimentary approach to it, but I want you to take it from there:
Iterate over the collection of all students you have in your roster.
If a student's name matches the current instance, then you've found your student.
If there is no student contained in the list that has the name, then you have to return a number that represents that there is no student there. 0 would be bad for averages as you can have an average score of 0; perhaps -1 would work.
A Map<String,Student> is the key here:
public class Roster {
private final Map<String, Student> students = new HashMap<>();
//Adds a grade to the end of a list of grades for the named student.
//Should work even if no student with this name has ever been seen before.
public void addGrade(String name, int score) {
Student stu = students.get(name);
if (stu == null) {
stu = new Student(name);
students.put(name, stu);
}
stu.addGrade(score);
}
//Gets the specified grade from the named student's scores.
public int getGrade(String name, int index) {
Student student = students.get(name);
if (student == null) {
throw new IllegalStateException("Student not found: " + name);
}
return student.getScore(index);
}
//Gets the average for the named student.
public double getAverage(String name) {
Student student = students.get(name);
if (student == null) {
throw new IllegalStateException("Student not found: " + name);
}
return student.getAverage();
}
}
Implement a class Student. For the purpose of this exercise, a student has a name and a total quiz score. Supply an appropriate constructor and methods getName(), addQuiz(int score), getTotalScore(), and getAverageScore(). To compute the latter, you also need to store the number of quizzes that the student took.
...
I am having an especially hard time with the scores and name. Do I add the scores to both the Student.java and the StudentTester.java files or only the tester? I cannot figure this out.
Here is my code:
/** A student has taken a number of quizzes and has an average score
based on the quizzes that were taken.
*/
public class Student
{
private String name;
private double totalscore;
private int numquiz;
}
// Constructs a student object with the name "MccStudent" and with zero total of quiz scores
public Student(String "mccStudent")
{
this.name = studentname;
numquiz = 0;
totalscore = 0;
}
public String getName()
{
return name;
}
// Adds the number of quizzes taken
public void addQuiz(double quizscore)
{
totalscore+=quizscore;
numquiz++;
}
// Returns the total quiz score
public double getTotalScore ()
{
return totalscore;
}
// Returns the avaerage grade
public double getAverageScore ()
{
return totalscore/numquiz;
}
}
/** Create a class to test the Student class.
*/
public class StudentTester
{
/**
Tests the methods of the Student class.
*/
public static void main(String[] args)
{
// Create an object
Student mccStudent = new Student();
mccStudent.addQuiz(100);
mccStudent.addQuiz(80);
mccStudent.addQuiz(95);
mccStudent.addQuiz(97);
System.out.println(mccStudent.getName());
System.out.println(mccStudent.getTotalScore());
// Display average quiz score
System.out.println(mccStudent.getAverage.Score());
}
}
First of all try to understand what Constructor is. Here is oracle documentation with nice examples: Constructor. Ill write simple example for you. Student is new Object that has String name attribute.
public class Student {
public String name; //name of student
public Student(String name) {//Constructor for student, receiving name when u create new object Student
this.name = name; //set received name to this public String name
}
/**
* When u call this method you will get inputed name from constructor
* so if u call Student stud = new Student("John");
* new Student("John") is constructor!
* with stud.getName(); you will get "John".
* This is called getter.
* #return name of student
*/
public String getName() {
return name;
}
}
There are some major issues here, for one the first brace is closed after declaring your instance variables, so the rest of the code for Student is out of scope. Remove the first close brace, and that should help that.
Another issue is the way you are using your constructor (the part that says public Student(String "mccstudents")). You need to providing a variable name there, and then whenever you make a new object, you pass in a string, and that will take the place of the variable name.
Not to sound like one of your teaches, you really shouldn't leave this to last minute To jump the gun on any number of people who will write a reply for this, this site is designed to help specific issues, not analyse the whole program.
Are there particular issues and concepts that you are not understanding that I can help with?
You add the fields to the class itself (Student.java).
Only code that performs tests should appear in the test class (StudentTester.java).
For example, say I have the 3 classes Person, Student and Teacher. The Person class would have general details about the people (name, age, email etc) which both the Student and Teacher class will extend. On top of this though, these classes will also have their own unique fields (e.g. wage & courseTaught (or "tought"?) for Teacher and schoolYear & classNumber for Student). If I just show the initial code I've got, maybe someone could point me in the right direction. Because Person doesn't have a courseTaught field, currently I'm just getting the output "Josh (null)" rather than "Josh (Computer Science)". Any help would be appreciated :)
public class Main {
public static void main(String args[]){
Teacher t = new Teacher("Josh", "Computer Science");
System.out.println(t.name + " (" + t.courseTaught + ")");
}
}
public class Person {
String name;
public Person(String pName){
name = pName;
}
}
public class Teacher extends Person{
String courseTaught;
public Teacher(String tName, String tCourseTaught){
super(tName);
}
}
The problem is simpler than you think. You're on the right track but you forgot to assign courseTaught in your Teacher constructor. The initial value of courseTaught is null and it stays that way because you never assign it to anything.
You'd want something like this:
public Teacher(String tName, String tCourseTaught){
super(tName); // <- takes care of Persons's field
courseTaught = tCourseTaught; // <- but don't forget to set the new field, too.
}
And yes, "taught" is the correct word.
As an aside, since you did tag your question "oop", you may want to check out this article on encapsulation for some information about the use of "getters" and "setters".
I'm battling at the moment in trying to understand how to approach this issue in an object-oriented way.
With a many-to-many relationship such as Students-Subjects, where each student gets a mark for a certain subject, assuming the following:
I want to be able to display all the marks for a given student.
I want to display all the marks from different students for a given subject
I want to be able to change any student's mark for a given subject.
I have trouble with this last one, I can't seem to think of a way to relate the classes to each other so that the marks will remain congruent when changed...
Here's what I was thinking about doing in pseudocode. Pretend we have 3 students each involved in 3 subjects (9 marks total):
Make a class for Student (String name, int studNumber)
Make a class for Subject (String name, int subNumber)
Make a class for Result(int percentageScore String grade(calculated based on
percentageScore))
Then have an array of arrays of Result objects, eg. [1][2] in the array will
give the score for the student with studNumber 2 in the subject with subNumber 1.
I feel like this isn't object-oriented? There should be some kind of acknoledgement of the relationship within the class design for subject and students. If that is indeed right, could anyone point me in the right direction? How does one do this in an object-oriented way?
Thanks a lot.
Why go with such complex class structures. You can have a simple Student class.
class Student{
String stuName;
long rollNo;
public Student(String stuName, long rollNo){
this.stuName=stuName;
this.rollNo=rollNo;
}
.
.
.
}
And a Subject class. Each subject has certain students enrolled and the marks that each student has scored in that subject. Which can be represented as:-
class Subject{
String subName;
HashMap<Integer,Student> Result;
public Subject(String subName){
this.subName=subName;
Result=new HashMap<Integer,Student>();
}
//add methods to add students,modify marks, etc
public void addStudent(String name,long roll, int marks){
Result.put(marks,new Student(name,roll));
}
public int giveMarksForSubject(long roll){
//iterate Results , and check for student objects to match roll no. return key of matching student
.
.
.
}
.
.
}
For the part where you mentioned you want to change marks for students for certain subject. You can search the Subject object by String name in your Main method's class and then change Student's marks according to name/rollNo. You can provide methods in Subject class for implementing such functionality.
Both subjects and grades have a limited number of values, so I suggest using enums:
public class Example {
public static void main(String[] args) throws IOException {
Student s1 = new Student("John Doe");
s1.setGrade(Subject.MATHS, Grade.B);
s1.setGrade(Subject.PHYSICS, Grade.A);
s1.setGrade(Subject.ENGLISH, Grade.E);
Student s2 = new Student("Jane Smith");
s2.setGrade(Subject.MATHS, Grade.C);
s2.setGrade(Subject.PHYSICS, Grade.C);
s2.setGrade(Subject.ENGLISH, Grade.A);
// print students and their grades:
s1.printAllGrades();
s2.printAllGrades();
// print every subject and its grades:
for(Subject s : Subject.values()){
s.printAllGrades();
}
}
}
enum Subject{
MATHS, PHYSICS, ENGLISH;
private Map<Student, Grade> grades = new HashMap<Student, Grade>();
public void setGrade(Student student, Grade grade){
grades.put(student, grade);
}
public void printAllGrades(){
System.out.println(this);
for(Student s : grades.keySet()){
System.out.println(s.getName() + " : " + grades.get(s));
}
}
}
enum Grade{
A, B, C, D, E, F
}
class Student{
private String name;
private Map<Subject, Grade> grades = new HashMap<Subject, Grade>();
public Student(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setGrade(Subject subject, Grade grade){
grades.put(subject, grade);
subject.setGrade(this, grade);
}
public Grade getGrade(Subject subject){
return grades.get(subject);
}
public void printAllGrades(){
System.out.println("Grades of " + name + ":");
for(Subject s : grades.keySet()){
System.out.println(s + " : " + grades.get(s));
}
}
}
Using the enum type is suitable to list both subjects and grades. It guarantees that only suitable values can be passed as an argument and is easily extensible - you can add a method to an enum if you wish. A simple HashMap for every student is enough to hold the mappings between subjects and grades.
You may want to read more on enums in java.
I think the approach should be same like with database tables. You should implement some sort of a "joining class" between these 2. That class should be singleton and you should reference it in both, students and subjects. The class should have some sort of a list or a map, or something with that structure, which would contain properties: student, subject, mark. That way you could iterate through that collection by any of those properties, which should do what you need. This example How to make SQL many-to-many same-type relationship table is for databases, but I think it should give you some helpful insight.
This article makes a very compelling case for including the following structures in your design:
Student (incl an array of Result pointers)
Subject (incl an array of Result pointers)
Result (with all the attributes that belong to the relationship)
Though the original post was a long time ago, hope this helps someone else.
I'm creating a web-based scholar system for students to look up their scores, view their schedule, etc. However, I'm having a problem on architecting this system, as in I can't find a suitable way to associate the data.
There's a student, which is in a (school) class. The student has a scoreboard. The (school) class has a list of the "assignments" the students had for each subject, but it only has informations such as name, maximum score, weight. The actual score sits on the student's scoreboard.
Many students are in the same class.
Only one instance of an assignment should exist at any time, and it should live in the SchoolClass object, because it's then applied to the whole class instead of per-student.
A student, then, should only hold it's own score, and reference the rest of the assignment data from outside.
How do i reference the specific homework from the student?
That was kind of confusing. This is what I currently have:
class Student extends Person {
private SchoolClass schoolClass;
private Scorecard scorecard;
}
class Subject {
private String name; /// "Compilers II", "Data Structures", etc.
}
class SchoolClass {
private Course course; // "Computer Science", "Administration", etc.
private List<Assignment> assignments;
class Assignment {
private Subject subject;
private int maxScore;
private int weight;
private String name; // "Test about material resistance II"
}
}
class Scorecard {
// How to reference each assignment from each subject in this student's class cleanly?
}
Is my design going on a good direction or should I just erase this and begin again? Thanks!
This is looking pretty good, but there are a couple things I would like to point out.
Is the Person classs abstract? If so then well done! If not it probably should be because person is a general term. For more information about when to make a class abstract check out my answer to this question.
Well done using Assignment as a nested class! It directly relates to SchoolClass so it should be nested, but how about the Subject class? That seems to be directly connected to SchoolClass as well therefore it would not be a bad idea to make Subject a nested class also.
As for referencing a single homework assignment, this depends on how you want to get it by. If you want to get it by index then use a getter. To do this you could simply put this code in SchoolClass:
public Assignment getAssignment(int index)
{
return assignments.get(index);
}
However if you want to use reference it by name instead it is a little more tricky, but still pretty strait-forward. You would add a getter to your Assignment class like this:
public String getName()
{
return name;
}
Then you would simply have to write another getter for SchoolClass like this:
public Assignment getAssignmentByName(String name)
{
for (Assignment assignment : assignments)
{
if (assignment.getName().equals(name))
return assignment;
}
System.out.println("No assignment found by the name of " + name);
return null;
}
Hope that helps! If you have any questions don't hesitate to ask!
Edit:
In order to let your assignment objects describe themselves they should override Object.toString(). The following code should be put in your assignment class.
// I noticed that you only have a maxScore variable, I think that a score variable is needed
private int score;
#Override
public String toString()
{
return "The score for this assignment is: " + score;
}