Podejście obiektowe inne niż w książce

0

Witam,
kawałek kodu w książce wygląda tak:

class Safe {
        private string safeCombination = "12345";
        private Jewels content = new Jewels();

        public Jewels Open(string combination) {
            if (combination == safeCombination)
                return content;
            return null;
        }

        public void PickLock(Locksmith lockpicker) {
            lockpicker.WriteDownCombination(safeCombination);
        }
    }

    class Locksmith {
        public void OpenSafe(Safe safe, Owner owner) {
            safe.PickLock(this);
            Jewels safeContents = safe.Open(writtenDownCombination);
            ReturnContents(safeContents, owner);
        }

        private string writtenDownCombination = null;
        public void WriteDownCombination(string combination) {
            writtenDownCombination = combination;
        }

        public virtual void ReturnContents(Jewels safeContents, Owner owner) {
            owner.ReceiveContent(safeContents);
        }
    }

    class JewelThief : Locksmith {
        private Jewels stolenJewels = null;

        public override void ReturnContents(Jewels safeContents, Owner owner) {
            stolenJewels = safeContents;
        }
    }

Natomiast moim zdaniem powinno wyglądać to tak:

    class Safe {
        private string safeCombination = "12345";
        private Jewels content = new Jewels();

        public Jewels Open(string combination) {
            if (combination == this.safeCombination)
                return content;
            return null;
        }

        public string PickLock(Locksmith lockpicker) {
            return this.safeCombination;
        }
    }

    class Locksmith {
        public Jewels OpenSafe(Safe safe) {
            string safeCode = safe.PickLock(this);
            return safe.Open(safeCode);
        }

        public virtual void ReturnContent(Safe safe, Owner owner) {
            Jewels safeJewels = OpenSafe(safe);
            owner.ReceiveContent(safeJewels);
        }
    }

    class JewelThief : Locksmith {
        Jewels stolenJewels = null;

        public override void ReturnContent(Safe safe, Owner owner) {
            Jewels safeJewels = OpenSafe(safe);
            this.stolenJewels = safeJewels;
        }
    }
  1. Dziwi mnie to, że czasem używamy .this a czasem nie. W książce jest return contents w klasie Safe. Przecież dotyczy to pola tego obiektu, więc dlaczego nie ma tam this?
  2. Czy pole writtenDownCombination w klasie Locksmith jest potrzebne? Przecież i tak nie mamy do niego dostępu. Ja pobieram kod do sejfu ale zapisuje w lokalnym stringu. Nie widzę potrzeby robienia z tego pola.
  3. Czy potrzebna jest metoda WriteDownCombination w klasie Locksmith? Ja po prostu zwracam string w PickLock. Nie rozumiem po co pisać:
public void WriteDownCombination(string combination) {
	this.writtenDownCombination = combination;
}

zamiast:

this.writtenDownCombination = PickLock(this); // wtedy PickLock zwróci string

Tutaj te dwa obiekty się ze sobą komunikują, ale moim zdaniem jest to takie mieszanie się z kodem. Locksmith wywołuje metodę PickLock która wywołuje metodę Locksmitha która ustawia writeDownCombination. Serio jest to potrzebne? Czy po prostu PickLock nie może zwracać stringa który ustawia writeDownCombination?
4. W książce metoda OpenSafe otwiera sejf i oddaje zawartość właścicielowi. Czy metoda nie powinna robić jednej rzeczy? Ja zrobiłem to tak, że zamiast void metoda zwraca zawartość sejfu, a zawartość zwracam właścicielowi wywołując metodę ReturnContent i w parametrze podając sejf. Tak wydaje mi się najbardziej logicznie.

Czytam tę książkę i sam już nie wiem, czy rozumuję prawidłowo, czy jednak podejście książkowe jest lepsze

1

Wersja książkowa jest prawidłowa, Twoja nie. Spójrz, co się dzieje w książkowej:

public void PickLock(Locksmith lockpicker) {
            lockpicker.WriteDownCombination(safeCombination);
        }

Sejf mówi ŚLUSARZOWI jak go może otworzyć. Dzięki temu ŚLUSARZ wie, jak otworzyć ten sejf. Zobacz, co się dzieje u Ciebie:

public string PickLock(Locksmith lockpicker) {
            return this.safeCombination;
        }

Sejf u Ciebie jest prostytutką, która daje każdemu. Wystarczy ją poprosić.

W przypadku książkowym muszę mieć ślusarza, żeby otworzyć sejf. W Twoim wystarczy, że wywołam metodę PickLock z nullem w parametrze i sejf mi poda swój kod. Chyba nie tak powinny działać sejfy, nie?

Rozumiesz już, czemu wersja książkowa jest prawidłowa?

Co do this. This to jest odwołanie do aktualnego obiektu z jego klasy. W takim przypadku:

class A
{
  int i;

  int Foo()
  {
    return i;
  }
}

Nie musisz używać this. Po prostu odwołujesz się do pola "i".

This jednak jest wymagane w takim przypadku:

class A
{
    int i;
   
    public A(int i)
    {
        this.i = i;
    }
}

Bez this w tym momencie kompilator przypisałby wartość zmiennej i - z parametru konstruktora do parametru konstruktora. This pokazuje, że chodzi Ci o pole z konkretnego obiektu.

Poza tym this jest używane, gdy przekazujesz instancję TEJ klasy do jakiejś innej. Np:

class A
{
    public void Foo()
    {
        B klasaB = new B(this); //<-- o tu
    }
}

class B
{
  A klasaA;

  public B(A klasa)
  {
    klasaA = klasa;
  }
}

Tylko w tych przypadkach this jest Ci potrzebne.

Ad. 2. Już Ci pokazałem, że robisz to źle. Zobacz, jak robi książka. Książka tylko podaje kod sejfu ślusarzowi i nic więcej. Ślusarz potem może otworzyć sejf za pomocą tego kodu. Więc musi go gdzieś sobie zapisać. W Twojej wersji sejf jest na tyle uprzejmy, że otwiera się przed każdym, kto go poprosi.

Ad. 3. Tak - to wszystko wynika z tego, co pisałem na samym początku. Że sejf nie może się otwierać przed każdym. Może jedynie po cichu podać swój kod ślusarzowi.

Ad. 4. Metoda powinna robić jedną rzecz. Tutaj możesz mieć rację. Jednak w tym prostym przykładzie EFEKTEM otwarcia sejfu jest pobranie jego zawartości. I tu liczy się efekt. Można to zrobić w taki sposób:

class Safe
{
    bool opened = false;
    string pass = "1234";

    public bool Open(string code)
    {
        opened = (pass == code);
        return opened;
    }

    public Jewels GetContent()
    {
        if(!opened)
            return null;
        else
            return jewels;
    }
    
}

W takim wypadku metoda Open TYLKO otwiera, natomiast metoda GetContent tylko zwraca swoją zawartość (jeśli sejf jest otwarty). Sam sobie odpowiedz na pytanie, czy w tym przypadku faktycznie ma to sens, czy nie :)

1

Zacznę od końca. Dlaczego użyli this? Bo mogli :) This nie przeszkadza, ale nie jest tu akurat potrzebne.

Teraz dalej. To:

public string FunnyThingIHave {get { return "Cześć dzieciaki"; }}

To jest właściwość. Natomiast to:

private string funnyThingIHave;

to jest zmienna. A konkretnie pole klasy (pole, czyli zmienna, która jest składnikiem klasy).

Wytłumaczę na przykładzie. Mamy taką klasę:

class TV
{
    public bool isOn;
    public TV
    {
        isOn = false;
    }

    public void On()
    {
        SprawdzNapiecie();
        SprawdzStanTelewizora();
        WczytajOstatnioOgladanyKanal();
        ZmienKolorDiody(Zielony);
        isOn = true;
    }
}

Prosta klasa. Telewizor. Możesz go uruchomić za pomocą metody On. W trakcie wykonywania tej metody, telewizor wykonuje pewne operacje. Na końcu zmienia stan pola isOn na true.

Jednak czasem trzeba sprawdzić, czy telewizor jest włączony. Dlatego pole isOn jest publiczne. Bo dzięki temu można zrobić to:

if(tv.isOn)
    Console.WriteLine("Telewizor włączony");

Ale przez to, że isOn jest publiczne, możesz zrobić też to:

if(!tv.isOn)
    tv.isOn = true;

if(tv.isOn)
    Console.WriteLine("Telewizor włączony");

Czy to jest dobry program? No zdecydowanie nie. Bo sama zmiana wartości zmiennej isOn nie powoduje włączenia telewizora. Dlatego też ludzie bardzo dawno temu doszli do wniosku, że upublicznianie pól klasy jest bardzo prostą i krótką drogą do katastrofy. Więc pola zaczęły być prywatne. Ale jakoś czasem trzeba je odczytać. Więc zaczęli stosować metody w stylu get. Tzw. gettery:

class TV
{
    private bool isOn;
    
    public bool GetIsOn()
    {
        return isOn;
    }
}

Czasem jednak też zaszła potrzeba, żeby móc modyfikować takie pola. Więc równocześnie zaczęto tworzyć metody set - tzw. settery:

class TV
{
    private bool isOn;
    
    public bool GetIsOn()
    {
        return isOn;
    }

    public void SetIsOn(bool value)
    {
        if(value)
          On();
        else
          Off();

        isOn = value;
    }
}

Czasami settery nie robią niczego innego jak tylko ustawiają wartość zmiennej.

Okazało się, że settery i gettery są wykorzystywane na tyle często (w każdym programie po wiele razy), że można z tego zrobić osobną konstrukcję. Wymyślono więc właściwości:

class TV
{
    private bool isOn; //prywatne pole klasy

    public bool IsOn
    {
        get { return isOn; }
        set
        {
            if(value)
              On();
            else
              Off();

             isOn = value;
        }
    }
    
}

Właściwości mają to do siebie, że skracają nieco zapis. Nie musisz tworzyć osobno dwóch metod, tylko jedną właściwość z dwoma blokami: get i set. Dodatkowo w bloku set masz dostępną zmienną value, która przechowuje odpowiednią wartość. Właściwości możesz używać tak:

TV tv = new TV();

bool tvIsOn = tv.IsOn;

tv.IsOn = true;

Czyli bardzo podobnie jak zmiennych. Właściwości to takie zmienne na sterydach. Mogą wykonać dodatkową akcję przed (po) zwróceniem wartości lub przed (po) zmianie wartości. Właściwości są zawsze publiczne, natomiast pola klasy zawsze prywatne.

Dużym problemem jest jeszcze kiedy używać właściwości, a kiedy metod. Np. co jest tutaj lepsze:

tv.IsOn = true;
//czy może
tv.On();

W tym wypadku akurat powinniśmy użyć metody, a nie właściwości, ale to zupełnie inny temat.

1 użytkowników online, w tym zalogowanych: 0, gości: 1