Sposoby zapewnienia unikalnych wystąpień klasy?

14

Szukam różnych sposobów, aby upewnić się, że każda instancja danej klasy jest instancją jednoznacznie identyfikowalną.

Na przykład mam Nameklasę z polem name. Gdy mam już Nameobiekt z namezainicjalizowaną wersją dla Johna Smitha, nie chcę być w stanie utworzyć instancji innego Nameobiektu, również o nazwie John Smith, lub jeśli wystąpi instancja, chcę raczej przekazać odwołanie do obiektu oryginalnego niż nowy obiekt.

Wiem, że jednym ze sposobów jest posiadanie statycznej fabryki, która przechowuje Mapwszystkie bieżące obiekty Name, i fabryka sprawdza, czy obiekt z Johnem Smithem, ponieważ nazwa nie istnieje, przed przekazaniem odwołania do Nameobiekt.

Innym sposobem, w jaki mogłem pomyśleć z góry, jest posiadanie statycznej mapy w Nameklasie i gdy wywoływany jest konstruktor, zgłaszający wyjątek, jeśli przekazywana wartość namejest już używana w innym obiekcie, jednak zdaję sobie sprawę z wyjątków w konstruktorze jest ogólnie złym pomysłem .

Czy istnieją inne sposoby osiągnięcia tego?

Arachid
źródło
1
Chcesz singletona
5
lepiej pójdź z pierwszą: - fabryka statyczna
Rohit Jain
16
@MarcB op nie chce singletona. może mieć wiele instancji tej samej klasy, ale te instancje muszą być możliwe do zidentyfikowania.
1
@MarcB Zdaję sobie sprawę z wzorca singletonu, ale pomyślałem, że zapewnia to tylko jeden przypadek klasy? Chcę wielu wystąpień, różnych wartości. Przepraszam, jeśli pytanie nie wyjaśnia tego. edycja: widziałem tylko pierwszy komentarz przed opublikowaniem.
Peanut
2
I'm aware that one way of doing this is to have a static factory that holds a Map...Dlaczego więc nie chcesz tego zrobić?
FrustratedWithFormsDesigner

Odpowiedzi:

13

Właściwie już odpowiedziałeś na swoje pytanie. Twój pierwszy sposób powinien być tutaj bardziej skuteczny. Używanie static factoryjest zawsze lepsze niż constructorgdziekolwiek myślisz, że możesz. Tak więc możesz uniknąć używania Constructorw tym przypadku, inaczej byłoby, throw some exceptiongdyby istniała już instancja o podanej nazwie.

Możesz więc utworzyć statyczną metodę fabryczną: - getInstanceWithName(name)która otrzyma już dostępną instancję o tej nazwie, a jeśli nie istnieje, utworzy nową instancję i uczyni twoją constructorprywatną, tak jak powinno się to zrobić w większości przypadków static factories.

Ponadto w tym celu musisz utrzymać statyczne Listlub Mapwszystkie utworzone unikalne wystąpienia w swojej Factoryklasie.

EDYCJA : -

Z pewnością powinieneś przejść przez - Skuteczna Java - Punkt 1: Rozważ fabryki statyczne nad Konstruktorami . Nie ma lepszego wyjaśnienia niż ta książka.

Rohit Jain
źródło
1
To jest coś, o czym OP już myślał.
BGurung,
+1 dla linku, który wydaje się wyraźnie uwzględniać niektóre obawy PO.
Robert Harvey
@RobertHarvey. Tak, ta książka jest najlepszym źródłem większości takich tematów. I to właściwie pierwszy przedmiot. :)
Rohit Jain
+1 za Effective Java, mam książkę, teraz właściwie siedzącą w mojej torbie :) Byłem jednak ciekawy innych sposobów osiągnięcia wyjątkowości oprócz statycznej metody fabryki / statycznej i wyjątku w konstruktorze. Jeśli nie ma, zaakceptuję to.
Peanut
@Arachid. Pewnie. To najlepsze źródło informacji, aby uzyskać więcej informacji.
Rohit Jain
5

Wzmianki o skutecznej Javie wydają się zwiększać wiarygodność, więc ta odpowiedź opiera się na:

  • Skuteczna pozycja Java 8: Przestrzegaj umowy ogólnej, gdy zastępowanie jest równe
  • Efektywna pozycja Java 9: ​​Zawsze zastępuj hashCode, gdy zastępujesz wartość równą
  • Skuteczna pozycja Java 15: minimalizacja zmienności

Cofnąłbym się i zapytałbym, dlaczego przejmujesz się, jeśli istnieje więcej niż jedna instancja tego obiektu nazwy.

Rzadko potrzebuję tego rodzaju łączenia obiektów. Sądzę, że OP robi to, aby mogli po prostu porównać swoje Nameobiekty ==. Lub użyj Nameobiektów wewnątrz HashMaplub podobnych jako klucza.

Jeśli tak, jest to coś, co można rozwiązać poprzez prawidłowe wdrożenieequals() .

Tak jak:

public final class Name {
  private final String name;

  public Name(String name) {
    if (name == null) {
      name = ""; //or exception if you like
    }
    this.name = name;
  }

  public String getName() {
    return name;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Name)) {
      return false;
    }
    Name other = (Name) o;
    return other.name.equals(name);
  }

  @Override
  public int hashCode() {
    return name.hashCode();
  }
}

Po wykonaniu tej czynności spełnione są następujące warunki:

Name a = new Name("weston");
Name b = new Name("weston");
assert(a.equals(b)); //same but:
assert(a!=b); //not the same instance
//test out the Name instances in a hashmap:
HashMap<Name,Object> map = new HashMap<Name,Object>();
Object detailsIn = new Object();
map.put(a,detailsIn);
Object detailsOut = map.get(b);
assert(detailsIn==detailsOut); //the map returned the same details object
//even though we put with `a` and got with `b` thanks to our correct equals implementation

Zgaduję twój cel, ale w ten sposób możesz wykorzystać Nameklasę w mapach skrótów itp. I nie muszą to być dokładnie te same wystąpienia.

weston
źródło
Przykład proszę.
Robert Harvey
@RobertHarvey przykład prawidłowej implementacji?
weston
Wygląda na to, że je dostarczyłeś. Ale nie jestem jasne, jak to się różni od zwykłego sprawdzania równości właściwości Name w statycznej metodzie fabrycznej.
Robert Harvey
1
@RobertHarvey Próbowałem wyjaśnić więcej, oferuję kompletną alternatywę, która wcale nie wymaga fabryki statycznej i rzucam wyzwanie punktowi wyjścia PO, że nie chcą mieć więcej niż jednej instancji na nazwę.
weston
Prawidłowym powodem, dla którego wymagane są unikalne obiekty, jest to, że jeśli chcesz, aby zsynchronizowany blok używał tego obiektu jako monitora. Dwa obiekty, które .equals () nie działałyby.
Leigh Caldwell,
3
  • Zrób Nameinterfejs
  • Utwórz interfejs NameFactoryza pomocą metodyName getByName(String)
  • Utwórz implementację NameFactoryz Map<String,WeakReference<Name>>wewnątrz
  • synchronizena mapie według nazwy wewnątrz getByNamemetody przed utworzeniem nowych instancjiName
  • Opcjonalnie użyj statycznej prywatnej implementacji Nameinterfejsu wewnątrz swojej implementacjiNameFactory

Takie podejście pozwoli ci upewnić się, że:

  • W danym momencie Nameistnieje tylko jedna instancja ,
  • W twojej klasie nie ma wycieku pamięci „Lingerer”, kiedy zostajesz Namedłużej, niż trzeba
  • Projekt pozostaje testowalny z wyśmiewanymi obiektami, ponieważ używasz interfejsów.
dasblinkenlight
źródło
Nie ma również wycieków pamięci przy użyciu metody fabrycznej, jeśli istnieje throwtaka sama nazwa zamiast zwracać obiekt, lub jeśli zwracany jest nullobiekt.
Robert Harvey
@RobertHarvey Mam na myśli wycieki, które nie są tak naprawdę przeciekami, ale uzasadnionymi obiektami (tak zwanymi „ociągaczami”). Prosta mapa statyczna uniemożliwiłaby zwolnienie jej obiektów wartości, natomiast mapa słabych odniesień utrzymywałaby je w pamięci tak długo, jak długo istniałyby inne aktywne odniesienia.
dasblinkenlight
Ach, rozumiem co masz na myśli. Może to być jeden z nielicznych przypadków, w których destructorprzydałby się.
Robert Harvey
1
Zbyt skomplikowane. Można to zrobić tak jak @weston answer, przesłonić równe i pozwolić fabryce zachować kolekcję nazw.
Tulains Córdova
@ user1598390 Należy uczynić to tak prostym, jak to możliwe, ale nie prostszym. „Rozwiązanie” Westona nie rozwiązuje problemu PO (nie ma mapy), a zbiór nazw powoduje przeciek pamięci w pamięci trwałej (tak, w Javie występują przecieki pamięci).
dasblinkenlight
1

powinieneś getNameInstance(String)ustawić konstruktory na prywatne i stworzyć metody, takie jak : jeśli obiekt o tej samej nazwie już istnieje (na przykład na podstawie klasy statycznej „hastable”), zwrócisz to odwołanie, w przeciwnym razie utworzysz nowy obiekt za pomocą prywatnego konstruktora i dodasz go do hashtable

HericDenis
źródło
Powtórzyłeś to, co powiedziałem w trzecim akapicie pytania. Szukałem alternatywnych sposobów.
Orzechowe
Przepraszam, myślę, że odpowiedzieliśmy prawie w tym samym czasie, ponieważ sprawdziłem odpowiedzi przed wysłaniem mojego. Właściwie próbowałem odpowiedzieć na StackOverflown, a następnie został zmigrowany.
HericDenis,
1

Spróbuj śledzić. Musisz śledzić każdy tworzony obiekt. W tym celu używam listy. I uczyniono konstruktorem klas prywatnym, aby można było zastosować kontrolę wstępną przed utworzeniem instancji

class UniqueName
    {
        private string Name;
        public int ID;
        private static int Count=0;
        static List<UniqueName> lt=new List<UniqueName>();

        private UniqueName(string Name)
        {
            this.Name = Name;
            ID = ++Count;
        }

        public static UniqueName GetUniqueueInstance(string Name)
        {
            foreach (UniqueName un in lt)
            {
                if ( string.Compare( un.Name,Name,StringComparison.InvariantCultureIgnoreCase)==0)
                    return un;
            }

            UniqueName temp=new UniqueName(Name);
            lt.Add(temp);
            return temp;
        }
    }
Akshay
źródło
To nie jest Java? Czy napisałeś pseudo-kod? Lub w innym języku? Proszę to wyraźnie określić.
Rohit Jain
Wygląda jak C #. Akshay, powinieneś nauczyć się innych kolekcji innych niż List. To byłoby dobre dla Dictionary<string,UniqueName>.
weston