C, PHP, VB, .NET

Дневникът на Филип Петров


* Изходен код от упражнение 10, 2015

Публикувано на 29 ноември 2015 в раздел ПИК3 Java.

В това упражнение продължаваме задачата от уражнение 9. Ще я разширим и ще поправим някои нарочно допуснати в миналия час грешки. Вземете сорс кода от упражнение 9 и променете основния клас да е с име Upr10.

Задача 5. В досега решените задачи се наблюдават проблеми със структурите от данни:

а) Възможно е за един човек да пазим, че е болен от една и съща болест множество пъти;

Решение: Ще покажем няколко възможни решения на тази точка.

Примерно (ЛОШО) решение 1: Вероятно най-лошия пример би бил да продължим да работим с ArrayList, но да го капсулираме така, че да не може да се получават повторения. Тоест променливата sicknesses в клас Patient трябва да стане private:

private ArrayList<Diagnosis> sicknesses;

След това добавяме методи addSickness и removeSickness (ние в настоящото решение не ги използваме въобще, но в реална задача биха били нужни). Всъщност може да се наложи да добавите и редица други методи, които дублират функционалност на клас ArrayList.

void addSickness(Diagnosis newSickness){
   for(Diagnosis d: sicknesses){
      // Не правим нищо ако вече я има
      if(d.equals(newSickness)) return;
   }
   this.sicknesses.add(newSickness);
}

void removeSickness(Diagnosis sicknessToRemove)
throws NoSuchSicknessException{
   Iterator it = this.sicknesses.iterator();
   while(it.hasNext()){
      if(it.next().equals(sicknessToRemove)){
         it.remove();
         return;
      }
   }
   throw new NoSuchSicknessException();
}

Сега трябва да защитим конструктора така, че да не може да бъде създаден човек, който да има една и съща болест два или повече пъти. Един (много бавен и неефективен!) вариант е да използваме току що създадените методи:

Patient(String name, String egn, double paycheck, ArrayList<Diagnosis> sicknesses){
   this.name = name;
   this.egn = egn;
   this.paycheck = paycheck;
   this.sicknesses = new ArrayList<>();
   for(Diagnosis d: sicknesses){
      this.addSickness(d);
   }
}

По-правилно (много по-бързо, но с известно разхищение на памет) решение е да променим списъка в множество:

Patient(String name, String egn, double paycheck, ArrayList<Diagnosis> sicknesses){
   this.name = name;
   this.egn = egn;
   this.paycheck = paycheck;
   Set<Diagnosis> tmp = new HashSet<>();
   tmp.addAll(sicknesses);
   this.sicknesses = new ArrayList<>();
   this.sicknesses.addAll(tmp);
}

Тук създадохме временен HashSet, в който натрупахме всички елементи на подаденото множество (дубликатите ще попадат на едно и също място в множеството, т.е. ще бъдат заменени) и после в списъка с болести добавихме всички елементи на това множество (а то знаем, че няма как да има повторения в себе си). Това е доста изкуствена операция, нали? Подсказва ни ясно, че сме избрали неправилно структурата от данни ArrayList в този клас. Защо просто не е HashSet?

Примерно (не чак толкова лошо) решение 2: Можем да използваме и най-обикновен масив. Това е реализируемо за конкретната задача само защото знаем, че имаме всичко на всичко 5 диагнози – това е строго фиксирано в изброимия тип. Добавяне на нови болести в нашата програма не е възможно без да променяме сорс кода на приложението (което практически значи, че работим с фиксирана структура).

Първо променяме типа данни:

private boolean[] sicknesses;

Слагаме методи за добавяне на нова болест и премахване на болест:

void addSickness(Diagnosis newSickness){
   this.sicknesses[newSickness.ordinal()] = true;
}
 
void removeSickness(Diagnosis sicknessToRemove){
   this.sicknesses[sicknessToRemove.ordinal()] = false;
}

Метод „ordinal“ дава индексът на елемента в изброимия тип. Първата константа в Enum е с индекс 0, втората с 1, и т.н., т.е. ще ни свърши идеална работа за индексите на масива. Сега променяме конструктора:

Patient(String name, String egn, double paycheck, List<Diagnosis> sicknesses){
   this.name = name;
   this.egn = egn;
   this.paycheck = paycheck;
   this.sicknesses = new boolean[Diagnosis.values().length];
   for(Diagnosis d: sicknesses){
      this.sicknesses[d.ordinal()] = true;
   }
}

В случая запазихме входния параметър да е структура от данни тип „списък“ (няма значение какъв). Можете да добавите и още един конструктор със структура от тип масив, но не забравайте да го валидирате, преди да го инициализирате. Входният параметър масив ще трябва да е от тип boolean и да е с дължина равна на броя на елементите на изброимия тип – в противен случай няма да има смисъл в тази задача.

Сега трябва да променим метод cureSickness. В него досега използвахме Iterator за обхождане на списъка. Вече нямаме такава нужда, защото знаем подадената диагноза на точно коя позиция в масива се намира:

double cureSickness(Diagnosis sickness)
throws OutOfMoneyException,NoSuchSicknessException{
   if(this.paycheck<=0) throw new OutOfMoneyException(sickness.cureCost);
   if(this.sicknesses[sickness.ordinal()] == false){
      throw new NoSuchSicknessException(sickness);
   }
 
   double result = this.payBill(sickness.cureCost);
   this.sicknesses[sickness.ordinal()] = false;
   return result;
}

Примерно решение 3: В часа беше подсказано да използвате EnumSet. Това е най-бързото и най-лесно за реализиране решение. Върнете оригиналния код от упражнение 9. В клас Person променете член променливата sicknesses да бъде от тип EnumSet:

EnumSet<Diagnosis> sicknesses;

След това променете и конструктора да приема такъв тип:

Patient(String name, String egn, double paycheck, EnumSet<Diagnosis> sicknesses){
...

И сега в основния клас Upr10 ще трябва да променим примерните данни (combination1, combination2 и combination3) по следния начин:

EnumSet<Diagnosis> combination1 = EnumSet.of(Diagnosis.LITTLESICK, Diagnosis.SICK);
EnumSet<Diagnosis> combination2 = EnumSet.of(Diagnosis.VERYSICK, Diagnosis.SICK);
EnumSet<Diagnosis> combination3 = EnumSet.of(Diagnosis.NOTSICK);

Готово! Останалият код е направен така, че обхождаме всичко с итератор и ще работи без проблем с новата структура от данни по същия начин, по който е работил със старата.

Имайте предвид, че вместо EnumSet<Diagnosis> можете спокойно да използвате HashSet<Diagnosis>. EnumSet е просто оптимизирана за работа с изброим тип версия на HashSet, в която допълнително са добавени няколко метода (например този, който използвахме – статичния of).

Примерно (вероятно най-добро) решение 4: Ние в крайна сметка ще използваме именно решение с HashSet<Diagnosis>. Причината за това е, че ще искаме да направим програмата разширяема за бъдещи подобрения. Ако по-нататък решим да променим Diagnosis да не е Еnum, а да е обикновен клас (съответно да можем да добавяме каквито и да е диагнози, а не предварително фиксирани), ЕnumSet ще ни спре – той работи само с изброим тип.

Тоест връщайки кода на оригиналното решение, правим следните промени:

// Променили сме член-променливата
HashSet<Diagnosis> sicknesses;

// Конструктор приемащ множество
Patient(String name, String egn, double paycheck, Set<Diagnosis> sicknesses){
   this.name = name;
   this.egn = egn;
   this.paycheck = paycheck;
   this.sicknesses = new HashSet<>(sicknesses);
}
// Конструктор приемащ списък - обратна съвместимост с оригиналното решение
Patient(String name, String egn, double paycheck, List<Diagnosis> sicknesses){
   this.name = name;
   this.egn = egn;
   this.paycheck = paycheck;
   this.sicknesses = new HashSet<>(sicknesses);
}

От тук нататък ще използваме примерно решение 4.

б) Ако един човек е посетил повече от един път болницата, като и при двата пъти се е случило да дължи пари, дълга от второто му посещение ще замени дълга от първото му посещение – така работи HashMap по принцип. Това не е коректно. Или трябва дълговете му да се натрупват в един общ, или трябва всеки отделен дълг да се пази самостоятелно.

Решение: Тук също можем да подходим по няколко начина. Ще разгледаме двата най-основни.

Примерно решение 1: Списъкът с длъжници принципно няма изрично вменена функция да пази отделните дължими суми от всяко различно посещение. Ако това ни устройва, можем да запазим структурата като HashMap, но при добавяне на нов запис да внимаваме да не презаписваме. В клас Upr10 досега правихме следното:

if(debt>0){
   Upr10.debtors.put(tmp, new Double(debt));
   System.out.println(tmp.name+" was put in the list of debtors with debt "+debt);
}

Променяме го по следния начин:

if(debt>0){
   Double oldDebt = debtors.get(tmp);
   if(oldDebt!=null) debt+=oldDebt;
   Upr10.debtors.put(tmp, new Double(debt));
   System.out.println(tmp.name+" was put in the list of debtors with debt "+debt);
}

С това всичко е готово. От тук нататък при всяко добавяне на длъжник ще трябва да правим това натрупване на стария дълг (ако го има) към новия.

Примерно решение 2: Можем да заменим HashMap с ArrayList, т.е. да позволим да има повторения в списъка на длъжниците. Това решение би било по-добро от предишното ако в бъдеще решим да разширяваме функционалността на този списък, като в него започнем да пазим по-детайлна информация (например подробна разбивка на коя дата какво точно се е случило). Това не е тривиална задача, защото HashMap работи коренно различно от ArrayList. Първо ще трябва да си създадем специален клас, чийто обекти ще описват различни длъжници:

class Debtor{
   Patient p;
   Double debt;
   Debtor(Patient p, Double debt){
      this.p = p;
      this.debt = debt;
   }
}

Забележка: Добре е в този клас debt да бъде капсулирана да е по-голямо или равно на нула число! Няма да правим това нещо, защото и в оригиналната задача не беше направено. Ще приемем, че при отрицателен debt болницата държи пари на пациента :)

Сега променяме статичната променлива debtors в клас Upr10:

static ArrayList<Debtor> debtors = new ArrayList<Debtor>();

От тук нататък там, където правим „Upr10.debtors.put(p, d)“, където p е Patient, а d е Double, трябва да променяме кода по следния начин: „Upr10.debtors.add(new Debtor(p,d))“. Това всъщност се случва на едно единствено място. По-надолу обаче имаме и използване на EntrySet, с който обхождаме длъжниците – това също трябва да се промени в обикновен forEach, защото ArrayList не работи с EntrySet. В крайна сметка клас Upr10 ще бъде променен по следния начин:

public class Upr10{
  static ArrayList<Debtor> debtors = new ArrayList<Debtor>();

  public static void main(String[] args){
    ArrayList<Diagnosis> combination1 = new ArrayList<>();
    combination1.add(Diagnosis.LITTLESICK);
    combination1.add(Diagnosis.SICK);
    ArrayList<Diagnosis> combination2 = new ArrayList<>();
    combination2.add(Diagnosis.VERYSICK);
    combination2.add(Diagnosis.SICK);
    ArrayList<Diagnosis> combination3 = new ArrayList<>();
    combination3.add(Diagnosis.NOTSICK);

    Patient p1 = new Patient("Ivan", "1234", 50.0, combination1);
    Patient p2 = new Patient("Petar", "5678", 150.0, combination2);
    Patient p3 = new Patient("Maria", "9012", 100.0, combination3);

    LinkedList<Patient> patients = new LinkedList<Patient>();
    patients.add(p1);
    patients.add(p2);
    patients.add(p3);

    for(Diagnosis sickness: Diagnosis.values()){
      Iterator<Patient> it = patients.iterator();
      while(it.hasNext()){
        Patient tmp = it.next();
        try{
          double debt = tmp.cureSickness(sickness);
          System.out.println(tmp.name+" was cured from "+sickness.toString());
          if(debt>0){
            Upr10.debtors.add(new Debtor(tmp, new Double(debt)));
            System.out.println(tmp.name+" was put in the list of debtors with debt "+debt);
          }
        }
        catch(OutOfMoneyException oe){
          System.out.println(tmp.name+" has no money to pay for "+sickness.toString());
        }
        catch(NoSuchSicknessException nse){
          //System.out.println(tmp.name+" is not sick from "+sickness.toString());
        }
      }
    }

    System.out.println("\nLIST OF OUR DEBTORS:");
    for (Debtor entry : Upr10.debtors){
      Patient patient = entry.p;
      Double debt = entry.debt;
      System.out.println(patient.name+" has debt "+debt.toString());
    }
  }
}

От тук нататък ще използваме примерно решение 2. Имайте предвид, че в упражнение 10 вие нямахте задача да променяте main метода на клас Upr10, защото реално в следваща задача (задача 8) вие трябваше да го напишете изцяло наново. Тук го променихме само и единствено с цел демонстрация на това какво би се променило в предишната задача при промяна в структурите от данни.

в) Освен това се случва нещо доста неправилно с парите на пациента – ако той се лекува от дадена болест и не му стигнат парите, веднъж се записва, че дължи определена сума в списъка на болницата (влиза в debtors), но и втори път неговия личен paycheck остава отрицателен. Така излиза, че хем е останал с отрицателен баланс и дължи тези пари на банката, хем ги дължи на болницата – т.е. дължи два пъти. Поправете този проблем. Възможен вариант е да капсулирате променливата paycheck в клас Patient така, че тя никога да не може да стане отрицателна:

  • Направете я private и ѝ направете съответни get метод и set метод, който да хвърля OutOfMoneyException със съобщение за грешка „The paycheck cannot be negative”;
  • При отрицателен paycheck конструктора да не позволява създаване на обекта;
  • Погрижете се в съществуващите методи променливата да не става отрицателна, а да спазва логиката на програмата – дълга се пази в списъка с длъжници, а не в променливата.

Решение: Реализирането на това не би трябвало да ви затрудни. В метод payBill на клас Patient трябва да добавите this.paycheck = 0, с което да нулирате парите на пациента при положение, че са станали отрицателни:

private double payBill(double amount){
   this.paycheck -= amount;
   if(this.paycheck >= 0) return 0;
   else{
      double debt = -this.paycheck;
      this.paycheck = 0;
      return debt;
   }
}

След това член-променливата трябва да стане private:

private double paycheck;

За нея да направите get и set методи:

double getPaycheck(){
  return this.paycheck;
}
 
void setPaycheck(double paycheck) throws OutOfMoneyException{
   if(paycheck<0) throw new OutOfMoneyException();
   this.paycheck = paycheck;
}

С това задача 5 е завършена.

Задача 6. Реализирайте клас MedicalRoom, в който пазите номер на стая (int number) и име на доктор, който работи в нея (String doctor). Направете опашка от пациенти, в който ще постъпват различни пациенти за лечение. Капсулирайте опашката с методи:

  • void queuePatient(Patient p) – добавя пациент в края на опашката;
  • boolean nextPatient() – излекува най-дълго чакалия пациент само от заболявания NotSick или LittleSick и го премахва от опашката. Напомняме, че при излекуването е възможно да се наложи да се обръщате към статичния списък debtors от основния клас. Метод nextPatient трябва да връща true ако след лечението пациента все още е болен – това ще се случи ако има допълнително Sick или VerySick болест, или пък е бил длъжник при лечение на NotSick и не са му стигнали пари за LittleSick.

Ако пациент е влязал в MedicalRoom, но не е бил болен от NotSick или LittleSick, nextPatient() просто връща true.

Примерно решение 1: Ще реализираме функционалността с обект от тип Queue. Не сме го разглеждали на упражнения, но той реално е много елементарен вариант на свързан списък. Трите му основни метода са add (добавя нов елемент), peek/element (връща първия добавен без го премахва) и poll/remove (премахва и връща първия добавен). Забележете, че в метод nextPatient() използваме практически фрагмент от кода на main метода на предишното упражнение:

class MedicalRoom{
  int number;
  String doctor;
  Queue<Patient> queue;
  // От какво се лекува пациента в MedicalRoom
  Diagnosis[] cureFrom;
  
  MedicalRoom(int number, String doctor){
    this.number = number;
    this.doctor = doctor;
    this.queue = new LinkedList<>();
    cureFrom = new Diagnosis[]{Diagnosis.NOTSICK, Diagnosis.LITTLESICK};
  }
  
  void queuePatient(Patient p){
    // Добавяме пациент в края на списъка
    this.queue.add(p);
  }
  
  // Ако хвърли изключение, опашката е празна
  boolean nextPatient() throws NoSuchElementException{  
    // Взимаме първия пациент от списъка
    Patient p = this.queue.poll();
    for(Diagnosis sickness: this.cureFrom){
      try{
        double debt = p.cureSickness(sickness);
        System.out.println(p.name+" was cured from "+sickness.toString());
        if(debt>0){
          Upr10.debtors.add(new Debtor(p, new Double(debt)));
          System.out.println(p.name+" was put in the list of debtors with debt "+debt);
        }
      }
      catch(OutOfMoneyException oe){
        System.out.println(p.name+" has no money to pay for "+sickness.toString());
        break;
      }
      catch(NoSuchSicknessException nse){}
    }
    // Проверяваме дали човека все още е болен от нещо
    for(Diagnosis sickness: Diagnosis.values()){
      if(p.sicknesses.contains(sickness)) return true;
    }
    return false;
  }
  
  boolean hasMorePatients(){
    return !this.queue.isEmpty();
  }
}

Примерно решение 2: Ако по-нататък решим да разширим функционалността на опашката ни като например дадем приоритет на едни пациенти спрямо други и т.н., Queue може да не ни върши работа. Затова можем да използваме по-силна структура с повече възможности – LinkedList:

class MedicalRoom{
  int number;
  String doctor;
  LinkedList<Patient> queue;
  // От какво се лекува пациента в MedicalRoom
  Diagnosis[] cureFrom;
  
  MedicalRoom(int number, String doctor){
    this.number = number;
    this.doctor = doctor;
    this.queue = new LinkedList<>();
    cureFrom = new Diagnosis[]{Diagnosis.NOTSICK, Diagnosis.LITTLESICK};
  }
  
  void queuePatient(Patient p){
    // Добавяме пациент в края на списъка
    this.queue.addLast(p);
  }
  
  // Ако хвърли изключение, опашката е празна
  boolean nextPatient() throws NoSuchElementException{  
    // Взимаме първия пациент от списъка
    Patient p = this.queue.getFirst();
    for(Diagnosis sickness: this.cureFrom){
      try{
        double debt = p.cureSickness(sickness);
        System.out.println(p.name+" was cured from "+sickness.toString());
        if(debt>0){
          Upr10.debtors.add(new Debtor(p, new Double(debt)));
          System.out.println(p.name+" was put in the list of debtors with debt "+debt);
        }
      }
      catch(OutOfMoneyException oe){
        System.out.println(p.name+" has no money to pay for "+sickness.toString());
        break;
      }
      catch(NoSuchSicknessException nse){}
    }
    // Премахваме го от опашката
    queue.removeFirst();
    // Проверяваме дали човека все още е болен от нещо
    for(Diagnosis sickness: Diagnosis.values()){
      if(p.sicknesses.contains(sickness)) return true;
    }
    return false;
  }
  
  boolean hasMorePatients(){
    return !this.queue.isEmpty();
  }
}

Задача 7. Реализирайте клас HospitalRoom наследяващ MedicalRoom, в който поставяте специално изискване – в опашката могат да влизат само и единствено пациенти, които имат заболявания Sick или VerySick. При това в такава стая и логиката на nextPatient() е обратна на MedicalRoom: не се лекуват NoSick или LittleSick, а само Sick и VerySick.

Забележете, че тук има съществена разлика с MedicalRoom – в HospitalRoom НЕ се позволява на пациент въобще да влиза ако той няма съответното заболяване. Така при капсулацията на данните ще се сблъскате с нов проблем – т.нар. „принцип на заместването на Лисков“ (https://en.wikipedia.org/wiki/Liskov_substitution_principle). Не е възможно наследника да хвърля задължително (checked) изключение в своя queuePatient метод, ако в базовия клас то не е дефинирано. С други думи казано в клас HospitalRoom няма да можете да предефинирате метода queuePatient като “queuePatient(…) throws NoSuckSicknessException”. За да заобиколите този проблем, направете нов NoSuchSicknessRuntimeException, който е незадължително изключение, и хвърляйте него. В Java е позволено наследник да предефинира метод да хвърля RuntimException дори ако базовия клас не го хвърля.

Решение:

class HospitalRoom extends MedicalRoom{
  HospitalRoom(int number, String doctor){
    super(number, doctor);
    cureFrom = new Diagnosis[]{Diagnosis.SICK, Diagnosis.VERYSICK};
  }
  
  void queuePatient(Patient p) throws NoSuchSicknessRuntimeException{
    boolean containsHospitalDiagnosis = false;
    for(Diagnosis sickness: this.cureFrom){
      if(p.sicknesses.contains(sickness)){
        containsHospitalDiagnosis = true;
        break;
      }
    }
    if(containsHospitalDiagnosis == false){
      throw new NoSuchSicknessRuntimeException();
    }
    super.queuePatient(p);
  }  
}

class NoSuchSicknessRuntimeException extends RuntimeException{
  NoSuchSicknessRuntimeException(){
    super("Пациентът нe е болен от болест за Hospital Room");
  }
  NoSuchSicknessRuntimeException(Diagnosis sickness){
    super("Пациентът нe е болен от тази болест: "+sickness.toString());
  }
}

Задача 8. В main метода на програмата създайте няколко обекта с пациенти. След това създайте две различни стаи. Направете им „GP офис“, който действа на следния принцип: ако пациентът има пари и има Sick или VerySick заболяване, изпраща го първо в HospitalRoom. След като даден пациент бъде излекуван в дадена стая, той трябва да се върне обратно в GP офиса. Там се проверява дали все още има пари и дали все още има заболявания и ако двете са вярни, праща човека към другата стая. Ако изначало пациента не е имал Sick или VerySick, трябва да бъде изпратен директно в MedicalRoom.

Решение: Тук в началото отново ще се възползваме от немалка част от кода на предишното упражнение.

import java.util.*;
public class Upr10{
  static ArrayList<Debtor> debtors = new ArrayList<Debtor>();
  
  public static void main(String[] args){
    // Създаване на няколко обекта с пациенти
    ArrayList<Diagnosis> combination1 = new ArrayList<>();
    combination1.add(Diagnosis.LITTLESICK);
    combination1.add(Diagnosis.SICK);
    ArrayList<Diagnosis> combination2 = new ArrayList<>();
    combination2.add(Diagnosis.LITTLESICK);
    combination2.add(Diagnosis.VERYSICK);
    combination2.add(Diagnosis.SICK);
    ArrayList<Diagnosis> combination3 = new ArrayList<>();
    combination3.add(Diagnosis.NOTSICK);
    Patient p1 = new Patient("Ivan", "1234", 50.0, combination1);
    Patient p2 = new Patient("Petar", "5678", 1500.0, combination2);
    Patient p3 = new Patient("Maria", "9012", 100.0, combination3);
    // Правим си списък с пациентите - опашка влизаща в GP офис
    List<Patient> GPQueue = new LinkedList<Patient>();
    GPQueue.add(p1);
    GPQueue.add(p2);
    GPQueue.add(p3);
    
    // Създаваме 2 стаи
    MedicalRoom medRoom = new MedicalRoom(1, "Ivanov"); 
    HospitalRoom hosRoom = new HospitalRoom(2, "Petrov");
    
    // Реализираме GP офиса - запределяме пациентите по стаите:
    Iterator<Patient> it = GPQueue.iterator();
    while(it.hasNext()){
      Patient p = it.next();
      if(p.sicknesses.contains(Diagnosis.SICK)
           ||
         p.sicknesses.contains(Diagnosis.VERYSICK)){
        hosRoom.queuePatient(p);
      }
      else medRoom.queuePatient(p);
      // Махаме го от GPQueue
      it.remove();
    }
    
    // Сега ще накараме докторите да поработят
    while(medRoom.hasMorePatients()){
      Patient theNextPatient = medRoom.queue.getFirst();
      boolean b = medRoom.nextPatient();
      
      if(b == false){
        System.out.println(theNextPatient.name+" left completely cured!");
        continue;
      }
      if(theNextPatient.getPaycheck()<=0){
        System.out.println(theNextPatient.name+" left still sick");
        continue;
      }
      try{
        hosRoom.queuePatient(theNextPatient);
        System.out.println(theNextPatient.name+" moved to hosRoom");
      }
      catch(NoSuchSicknessRuntimeException e){
        System.out.println(theNextPatient.name+" left still sick");
      }          
    }
    System.out.println("--- Medical room ended first pass! ---");
    
    while(hosRoom.hasMorePatients()){
      Patient theNextPatient = hosRoom.queue.getFirst();
      boolean b = hosRoom.nextPatient();
      if(b == false){
        System.out.println(theNextPatient.name+" left completely cured!");
        continue;
      }
      if(theNextPatient.getPaycheck()<=0){
        System.out.println(theNextPatient.name+" left still sick");
        continue;
      }
      medRoom.queuePatient(theNextPatient);
      System.out.println(theNextPatient.name+" moved to medRoom");
    }
    System.out.println("--- Hospital room ended first pass! ---");
    
    while(medRoom.hasMorePatients()){
      Patient theNextPatient = medRoom.queue.getFirst();
      boolean b = medRoom.nextPatient();
      
      if(b == false){
        System.out.println(theNextPatient.name+" left completely cured!");
        continue;
      }
      if(theNextPatient.getPaycheck()<=0){
        System.out.println(theNextPatient.name+" left still sick");
        continue;
      }
      try{
        hosRoom.queuePatient(theNextPatient);
        System.out.println(theNextPatient.name+" moved to hosRoom");
      }
      catch(NoSuchSicknessRuntimeException e){
        System.out.println(theNextPatient.name+" left still sick");
      }          
    }
    System.out.println("--- Medical room ended second pass! ---");
    
    while(hosRoom.hasMorePatients()){
      Patient theNextPatient = hosRoom.queue.getFirst();
      boolean b = hosRoom.nextPatient();
      if(b == false){
        System.out.println(theNextPatient.name+" left completely cured!");
        continue;
      }
      if(theNextPatient.getPaycheck()<=0){
        System.out.println(theNextPatient.name+" left still sick");
        continue;
      }
      medRoom.queuePatient(theNextPatient);
      System.out.println(theNextPatient.name+" moved to medRoom");
    }
    System.out.println("--- Hospital room ended second pass! ---");
               
  }
}

ОСТАНАЛАТА ЧАСТ ОТ КРАЙНИЯ СОРС КОД (всичко направено дотук без горния клас с main метод):

enum Diagnosis{
  NOTSICK(10.0), LITTLESICK(20.0), SICK(50.0), VERYSICK(200.0);
  final double cureCost;
  Diagnosis(double cureCost){
    this.cureCost = cureCost;
  }
}

class OutOfMoneyException extends Exception{
  OutOfMoneyException(){
    super("Пациентът няма достатъчно пари");
  }
  OutOfMoneyException(double cureCost){
    super("Пациентът няма достатъчно пари. Трябва да плати: "+cureCost);
  } 
}

class NoSuchSicknessException extends Exception{
  NoSuchSicknessException(){
    super("Пациентът нe е болен от тази болест");
  }
  NoSuchSicknessException(Diagnosis sickness){
    super("Пациентът нe е болен от тази болест: "+sickness.toString());
  }
}

class Patient{
  String name;
  String egn;
  private double paycheck;
  HashSet<Diagnosis> sicknesses;
  Patient(String name, String egn, double paycheck, Set<Diagnosis> sicknesses){
    this.name = name;
    this.egn = egn;
    this.paycheck = paycheck;
    this.sicknesses = new HashSet<>(sicknesses);
  }
  
  Patient(String name, String egn, double paycheck, List<Diagnosis> sicknesses){
    this.name = name;
    this.egn = egn;
    this.paycheck = paycheck;
    this.sicknesses = new HashSet<>(sicknesses);
  }
  
  double getPaycheck(){
    return this.paycheck;
  }
  
  void setPaycheck(double paycheck) throws OutOfMoneyException{
    if(paycheck<0) throw new OutOfMoneyException();
    this.paycheck = paycheck;
  }
  
  private double payBill(double amount){
    this.paycheck -= amount;
    if(this.paycheck >= 0) return 0;
    else{
      double debt = -this.paycheck;
      this.paycheck = 0;
      return debt;
    }
  }
  
  double cureSickness(Diagnosis sickness)
    throws OutOfMoneyException,NoSuchSicknessException{
    if(this.paycheck<=0) throw new OutOfMoneyException(sickness.cureCost);
    Iterator<Diagnosis> it = this.sicknesses.iterator();
    while(it.hasNext()){
      if(it.next().equals(sickness)){
        double result = this.payBill(sickness.cureCost);
        it.remove();
        return result;
      }
    }
    throw new NoSuchSicknessException(sickness);
  }
  
  public int hashCode(){
    int result = 17;
    result = result*37 + this.name==null?0:this.name.hashCode();
    result = result*37 + this.egn==null?0:this.egn.hashCode();
    result = result*37 + (this.sicknesses==null?0:this.sicknesses.hashCode());
    return result;
  }
  
  public boolean equals(Object o){
    if(!(o instanceof Patient)) return false;
    Patient p = (Patient)o;
    if(this.name == p.name
         &&
       this.egn == p.egn
         &&
       this.sicknesses.equals(p.sicknesses)
      ) return true;
    else return false;
  }
}

class Debtor{
  Patient p;
  Double debt;
  Debtor(Patient p, Double debt){
    this.p = p;
    this.debt = debt;
  }
}

class MedicalRoom{
  int number;
  String doctor;
  LinkedList<Patient> queue;
  // От какво се лекува пациента в MedicalRoom
  Diagnosis[] cureFrom;
  
  MedicalRoom(int number, String doctor){
    this.number = number;
    this.doctor = doctor;
    this.queue = new LinkedList<>();
    cureFrom = new Diagnosis[]{Diagnosis.NOTSICK, Diagnosis.LITTLESICK};
  }
  
  void queuePatient(Patient p){
    // Добавяме пациент в края на списъка
    this.queue.addLast(p);
  }
  
  // Ако хвърли изключение, опашката е празна
  boolean nextPatient() throws NoSuchElementException{  
    // Взимаме първия пациент от списъка
    Patient p = this.queue.getFirst();
    for(Diagnosis sickness: this.cureFrom){
      try{
        double debt = p.cureSickness(sickness);
        System.out.println(p.name+" was cured from "+sickness.toString());
        if(debt>0){
          Upr10.debtors.add(new Debtor(p, new Double(debt)));
          System.out.println(p.name+" was put in the list of debtors with debt "+debt);
        }
      }
      catch(OutOfMoneyException oe){
        System.out.println(p.name+" has no money to pay for "+sickness.toString());
        break;
      }
      catch(NoSuchSicknessException nse){}
    }
    // Премахваме го от опашката
    queue.removeFirst();
    // Проверяваме дали човека все още е болен от нещо
    for(Diagnosis sickness: Diagnosis.values()){
      if(p.sicknesses.contains(sickness)) return true;
    }
    return false;
  }
  
  boolean hasMorePatients(){
    return !this.queue.isEmpty();
  }
}

class HospitalRoom extends MedicalRoom{
  HospitalRoom(int number, String doctor){
    super(number, doctor);
    cureFrom = new Diagnosis[]{Diagnosis.SICK, Diagnosis.VERYSICK};
  }
  
  void queuePatient(Patient p) throws NoSuchSicknessRuntimeException{
    boolean containsHospitalDiagnosis = false;
    for(Diagnosis sickness: this.cureFrom){
      if(p.sicknesses.contains(sickness)){
        containsHospitalDiagnosis = true;
        break;
      }
    }
    if(containsHospitalDiagnosis == false){
      throw new NoSuchSicknessRuntimeException();
    }
    super.queuePatient(p);
  }  
}

class NoSuchSicknessRuntimeException extends RuntimeException{
  NoSuchSicknessRuntimeException(){
    super("Пациентът нe е болен от болест за Hospital Room");
  }
  NoSuchSicknessRuntimeException(Diagnosis sickness){
    super("Пациентът нe е болен от тази болест: "+sickness.toString());
  }
}

Допълнителна задача 1. Помислете как може да подобрите капсулацията на класовете MedicalRoom и HospitalRoom. Какво бихме могли да спечелим от гледна точка на капсулацията на данни, ако направим един абстрактен клас Room и те да го наследяват самостоятелно, вместо досегашната реализация? Направете го!

Допълнителна задача 2. В main метода ни се налага да „изчистваме“ опашките на два паса. Защо?

Допълнителна задача 3. Защо се налага на метод nextPatient() да прави throws NoSuchElementException?

Допълнителна задача 4. В момента реализацията на nextPatient връща boolean и премахва пациента от опашката. По този начин ние можем да узнаем, че отпратения пациент е все още болен, но нямаме никаква идея кой е този пациент. Това е и причината в main метода да се наложи да си го пазим като временна променлива (Patient theNextPatient = hosRoom.queue.getFirst()). Можете ли да предложите промяна в логиката на приложението като цяло така, че да не ни се налага да правим такива изкуствени действия? Например вместо да връща boolean, nextPatient() да връща самия пациент, а в клас Patient да направите метод „boolean hasMoreSicknesses()“? Направете го.

Допълнителна задача 5. След като сте рeшили допълнителна задача 4, помислете как да се отървете от проблема “ throws NoSuchElementException“ на метод nextPatient() – направете така, че класа никога да не хвърля изключение.

Допълнителна задача 6. След като сте решили всички досегашни допълнителни задачи, помислете как да разчистим кода така, че класовете MedicalRoom и HospitalRoom да не правят System.out.println никъде, а да делегираме тази отговорност само и единствено на main метода. По този начин ще получим значително по-изчистени взаимоотношения между обектите и кодът ще е много по-мобилен и по-ясен. Направете го.

Допълнителна задача 7. Правилно ли е метод cureFrom да е част от клас Patient? Пациентът сам ли се лекува от болестите? Не е ли по-правилно докторът в съответната стая да го прави?

Допълнителна задача 8. Нужни ли са толкова много изключения по принцип? Измислете вариант да реализирате същата задача без да бъде хвърляно нито едно изключение никъде (това разбира се ще изиска и нова философия за някои функционалности).

 



Добави коментар

Адресът на електронната поща няма да се публикува


*