Kirjautuminen

Haku

Tehtävät

Keskustelu: Yleinen keskustelu: Protected jäsenmuuttuja - mitä järkeä?

Sivun loppuun

vesikuusi [24.09.2015 21:31:36]

#

(Huom. tämä ei koske protected-metodeja.)

Ymmärrän, miksi on vaarallista määritellä jäsenmuuttujan näkyvyys protected-tyyppiseksi. Olen hiljattain mietiskellyt, että mitä järkeä sellaisessa jäsenmuuttujassa voi edes olla. Ainoa keksimäni käytännön esimerkki on MVC-frameworkin Model, silloin kun kyseessä on kieli joka sallii perittävän luokan nähdä perivän luokan protected-jäsenet (mikä on kyllä hieman perverssiä). Tällaisen MVC-jutun voisi toki toteuttaa ilmankin protected-jäsenmuuttujia - protectedeilla Model olisi vaan ehkä kivempi käyttää.

Fakta: jäsen määritellään privateksi silloin, kun halutaan estää sen näkyminen suoraan luokan ulkopuolelle. Luokka voi luottaa siihen, että jäsen ei muutu jos se ei itse sitä muuta.

Toinen fakta: Jos jäsenmuuttuja onkin protected, menetetään edellä mainittu takuu. Nyt luokka ei voi enää mitenkään olettaa, mitä jäsen sisältää milloinkin, sillä siihen voidaan kirjoittaa luokan ulkopuolelta.

Siis luokan kannalta ei ole mitään eroa, onko jäsenmuuttuja protected vai public.

Kerroin jo ainoan käytännön esimerkkini protected-jäsenen puolesta. Teoreettinen esimerkkini on se, että jos perittävä luokka tarvitsee jonkin tiedon perivältä luokalta niin kuin template method patternissa, voidaan tätä tiedonhakua nopeuttaa protected jäsenmuuttujalla (vertaa protected metodin kutsumiseen). Tämä vaan kuulostaa minusta aikamoiselta mikro-optimoinnilta.

Tämä on aika simppeli kysymys, ja joillekin varmasti itsestäänselvä asia. Tahtoisin kuitenkin käytännön esimerkkejä siitä, milloin on järkevää tehdä jäsenmuuttujasta protected, sillä niitä [esimerkkejä] vaikuttaa olevan todella vähän.

groovyb [24.09.2015 23:07:55]

#

Protected on käytössä, kun ei haluta että suoraa instanssia pystytään luomaan.
Otetaan tästä esimerkkinä Factory -pattern leikkikenttä käyttäen protectedia:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FactoryPlayground
{
    class Program
    {
        static void Main(string[] args)
        {
            var configFactory = ConfigFactory.New();
            var ServiceObj = ServiceObject.New(configFactory);
        }
    }

    public interface IServiceObject
    {
        string connection { get;}
    }

    class ServiceObject : IServiceObject
    {

        private string _connection;
        public string connection
        {
            get { return _connection;}
        }

        public static IServiceObject New(IConfigFactory factory)
        {
            return new ServiceObject(factory.SomeConfigValue);
        }

        protected ServiceObject(string connection)
        {
            this._connection = connection;
        }
    }

    public interface IConfigFactory
    {
        string SomeConfigValue { get; set;}
    }

    class ConfigFactory : IConfigFactory
    {
        public string SomeConfigValue { get; set; }

        public static IConfigFactory New()
        {
            return new ConfigFactory();
        }

        protected ConfigFactory()
        {
            this.SomeConfigValue = "Jotain";
        }
    }
}

Näin ollen ainut mikä pystyy luomaan ServiceObjectin, on ConfigFactory.

vesikuusi [24.09.2015 23:28:26]

#

Kiitos groovyb, mutta kirjoitin jäsenmuuttujista nimenomaan. Rajasin metodit pois tästä viestini alussa (no, konstruktoria ei ehkä lueta metodiksi, mutta...). Jäsenen näkyvyyden merkitys muuttuu valtavasti kun puhutaankin metodeista ja konstruktoreista.

Keskustellaan nyt siis jäsenmuuttujista. Mutta ihan hyvä viesti noin muuten. :)

groovyb [24.09.2015 23:34:41]

#

No mitään järkeä sitä ei ole kyllä pelkästään jäsenmuuttujiin käyttää. En ainakaan nopeasti keksi yhtään hyväksyttävää syytä.

*edit*
Ehkä jotain tän tapaista voisi viritellä:

public class LockBase
 {
     protected object lockObj = new object();
 }

 public class ThreadObjA : LockBase
 {
     public void DoSomething()
     {
         lock(lockObj)
         {
             //....
         }
     }
 }

 public class ThreadObjB : ThreadObjA
 {
     public void DoSomethingElse()
     {
         lock(lockObj)
         {
             //....
         }
     }
 }

vesikuusi [24.09.2015 23:53:14]

#

groovyb kirjoitti:

No mitään järkeä sitä ei ole kyllä pelkästään jäsenmuuttujiin käyttää. En ainakaan nopeasti keksi yhtään hyväksyttävää syytä.

Okei. Näin olen ajatellut itsekin. Silti näitä protected-kenttiä näkyy tuon tuostakin kaupallisessa ja ei-kaupallisessa koodissa. Tässä taitaa olla yleensä kysymys vain huonosta suunnittelusta?

Lisäys: Kiitos tuosta viimeisestä esimerkistä.

groovyb [24.09.2015 23:59:10]

#

No ei näkyvyyden hallinta huonoa suunnittelua ole. Protected, kuten internal ja muutkin access modifierit ovat käytännössä arkkitehtuuria rajaavia tekijöitä, joilla koodia pakotetaan tekemään samalla muotilla. Eli rajaamalla näkyvyyttä saadaan koodarit käyttämään vakiomenetelmiä ja patterneja. Asia erikseen on sitten kirjastot, joissa näkyvyyksillä halutaan rajata tapaa miten kirjastoa käytetään (vendor -kirjastot), kun alkuperäistä lähdekoodiakaan ei ole saatavilla. Jos itselle tai yksin koodaa, ei näillä asioilla ole juurikaan merkitystä. Jos käytetään ulkopuolista kirjastoa, on näkyvyys todennäköisesti määritetty niin että ainoastaan sallittuja asioita nähdään, ja pystyy koodissa räpläämään. Jos taas tehdään suurempaa projektia useammalla koodarilla, näkyvyydet määritellään jotta arkkitehtuuri pysyisi läjässä.

jlaire [25.09.2015 01:06:14]

#

Synkronisointiin käytettävien muuttujien kannattaa tavallisesti olla privaatteja, koska jos kaksi eri luokkaa käyttävät samaa muuttujaa niin deadlockien estäminen vaatii paljon huolellisuutta.

Erityisesti saman muuttujan jakaminen perivän ja perittävän luokan välillä on minusta tosi pelottava ajatus, mutta ehkäpä sille on joku käyttötarkoitus.

Kuitenkin, onko perinnän käytössä tähän joku etu DI:hin verrattuna? Sinänsähän minkä tahansa jäsenmuuttujan voisi alustaa perinnällä, mutta dependency injection on yksinkertaisempi ja joustavampi ratkaisu.

public class A
{
    private readonly object lockObj;
    public A(object lockObj) { this.lockObj = lockObj; }
    public void DoSomething() { lock (lockObj) { /* ... */ } }
}

public class B { /* vastaavasti */ }

Lisäys:
Jos haluaa jakaa saman lockObj:n useamman eri luokan kanssa, sen voi tehdä kätevästi Factoryllä.

public class Factory
{
     private readonly object lockObj = new object();
     public A CreateA() { return new A(lockObj); }
     public B CreateB() { return new B(lockObj); }
}

groovyb [25.09.2015 01:13:17]

#

Tuo oli vain esimerkki protectedista, mutta eihän tuossakaan mallissa eri luokat samaa muuttujaa käytä. Jokaisessa instanssissa kyseinen muuttuja on omansa. Vai mitä tarkoitit?

ThreadObjA first = new ThreadObjA();
first.Something();
ThreadObjB second = new ThreadObjB();
second.Something();
ThreadObjB third = new ThreadObjB();
third.SomethingElse();

//Noilla ei oo mitään tekemistä keskenään. Jokaisessa luokkainstassissa myös protected LockObj on oma instanssinsa

jlaire [25.09.2015 01:15:46]

#

Jos ThreadObjB perii ThreadObjA:n, ne käyttävät samaa lockObj-objektia. Eli jos B kutsuu jotain base-metodia, siinä on deadlockin vaara, eikö?

groovyb [25.09.2015 01:20:50]

#

No eihän käytä. Samannimistä, mutta jokaisessa instanssissa lockObj on uusi oma objektinsa. eli jos kutsut base -metodia, on se sisäinen kutsu, jolla ei ole mitään tekemistä muiden ThreadObjA instanssien kanssa.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FactoryPlayground
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            B b = new B();

            Console.WriteLine(a.model.value);
            Console.WriteLine(b.model.value);

            a.model.value = "Tämä on A";

            Console.WriteLine(a.model.value);
            Console.WriteLine(b.model.value);

            b.model.value = "Tämä on B";

            Console.WriteLine(a.model.value);
            Console.WriteLine(b.model.value);

            Console.ReadLine();

        }



    }


    public class BaseModel
    {
        public string value { get; set; }
        public BaseModel()
        {
            value = "Tämä on perusarvo";
        }
    }

    public class Base
    {
        public BaseModel model = new BaseModel();
    }


    public class A : Base
    {

    }

    public class B : A
    {

    }



}

Output:

Tämä on perusarvo
Tämä on perusarvo

Tämä on A
Tämä on perusarvo

Tämä on A
Tämä on B

jlaire [25.09.2015 01:24:48]

#

jlaire kirjoitti:

Eli jos B kutsuu jotain base-metodia, siinä on deadlockin vaara, eikö?

Lisätäänpä yksi metodikutsu tuohon alkuperäiseen esimerkkiisi:

public class LockBase
{
    protected object lockObj = new object();
}

public class ThreadObjA : LockBase
{
    public void DoSomething()
    {
        lock(lockObj)
        {
            //....
        }
    }
}

public class ThreadObjB : ThreadObjA
{
    public void DoSomethingElse()
    {
        lock(lockObj)
        {
            // Kutsutaan base-luokan metodia:
            DoSomething();
        }
    }
}

Kun luokat ovat eri tiedostoissa, tämä on minusta edelleen pelottava ja riskialtis patterni.

groovyb [25.09.2015 01:32:52]

#

Vaikka Taskit on käynnissä samaan aikaan ja DoSomethingin aikaviive on 5sek, DoSomething valmistuu ensin koska lukko käytössä.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FactoryPlayground
{
    class Program
    {
        static void Main(string[] args)
        {
            ThreadObjB B = new ThreadObjB();

           Task a = new Task(() => B.DoSomething());
           Task b = new Task(() => B.DoSomethingElse());
           Task c = new Task(() => B.DoSomething());
           Task d = new Task(() => B.DoSomethingElse());
           Task e = new Task(() => B.DoSomething());
           Task f = new Task(() => B.DoSomethingElse());

           a.Start();
           b.Start();
           c.Start();
           d.Start();
           System.Threading.Thread.Sleep(1000);
           e.Start();
           f.Start();
           Console.ReadLine();

        }


        public class LockBase
        {
            protected object lockObj = new object();
        }

        public class ThreadObjA : LockBase
        {
            public void DoSomething()
            {
                lock (lockObj)
                {
                    System.Threading.Thread.Sleep(5000);
                    Console.WriteLine("DoSomething Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                }
            }
        }

        public class ThreadObjB : ThreadObjA
        {
            public void DoSomethingElse()
            {
                lock (lockObj)
                {
                    System.Threading.Thread.Sleep(2000);
                    Console.WriteLine("DoSomethingElse Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                    DoSomething();
                }
            }
        }
    }
}

jlaire [25.09.2015 01:35:08]

#

Voit tehdä vaikka miljoona luokkaa ja metodia, jotka kaikki käyttävät samaa lukkoa, mutta jotka sattumalta eivät mene deadlockiin. Se ei tarkoita, että koodi on järkevästi suunniteltu.

En pysty keksimään mitään tapausta, jossa tällainen perintä olisi paras ratkaisu.

groovyb [25.09.2015 01:38:53]

#

Idea on että yhden instanssin sisällä on sama lukko. ei se eroa siitä kuin että olisi tehnyt luokkaasi yhden private objectin lukolle, jota kaikki metodisi käyttävät. Se että sijaitsee eri tiedostossa, ei ole mitään merkitystä asian kannalta.

Kokeile ajaa tuo ylläoleva koodi ja leiki breakpointeilla, ehkä asia aukeaa.

Ja tässä esimerkissä nimenomaan halutaan käyttää samaa lukkoa, jotta estetään deadlock. Sehän lukon tarkoitus on, estää race condition ja deadlock, jotta kaksi säiettä ei pääse tekemään samaa asiaa samaan aikaan. Ei ole mitään järkeä käyttää eri metodeille eri lukkoa jos metodit käsittelevät samaa kohdetta, tuohan nimenomaan sallisi race conditionin.

jlaire [25.09.2015 01:53:18]

#

Ymmärrän kyllä, mitä tuossa koodissa tapahtuu, se mitä en ymmärrä on että miksi joku tekisi näin. Lukko-objektin voisi ihan hyvin ottaa konstruktorissa parametrinä ja tallentaa privaattiin muuttujaan, kuten ensimmäisessä viestissäni näytin.

groovyb kirjoitti:

Se että sijaitsee eri tiedostossa, ei ole mitään merkitystä asian kannalta.

Yhden luokan ja privaatin jäsenmuuttujan tapauksessa voi vakuuttua siitä, että koodi ei voi missään tapauksessa aiheutta deadlockia.

Mutta tässä kuka tahansa voi periä luokista ThreadObjA/TreadObjB, ja myöhemmin kaikki muutokset niihin voivat aiheuttaa ongelmia. Joutuu aina käymään koko luokkahierarkian läpi, kun haluaa varmistua koodin toimivuudesta.

groovyb [25.09.2015 01:57:11]

#

tottakai voi, annoin vain esimerkin protectedin käytöstä alunperin.

Ja toki tuossakin voisi constructor objectin ottaa vastaan, joka sijoittuisi sisäiseen protected muuttujaan, jos halutaan hoitaa myös eri instanssien välillä pyörivä lukotus, eikä pelkästään luokan sisäinen lukotus, josta tuo esimerkkini oli.

Lisäys:

eli näin:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FactoryPlayground
{
    class Program
    {
        static void Main(string[] args)
        {

            object _lock = new object();

            for(int i = 0; i < 500; i++)
            {
                ThreadObjA a = new ThreadObjA(_lock);
                ThreadObjB b = new ThreadObjB(_lock);
                Task a_task = new Task(() => a.DoSomething());
                Task b_task = new Task(() => b.DoSomethingElse());
                a_task.Start();
                b_task.Start();
            }
            Console.ReadLine();
        }


        public class LockBase
        {
            protected object lockObj {get;set;}
        }

        public class ThreadObjA : LockBase
        {
            public ThreadObjA(object _lock)
            {
                base.lockObj = _lock;
            }
            public void DoSomething()
            {
                lock (lockObj)
                {
                    System.Threading.Thread.Sleep(300);
                    Console.WriteLine("DoSomething Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

                }
            }
        }

        public class ThreadObjB : ThreadObjA
        {
            public ThreadObjB(object _lock) : base(_lock) {}
            public void DoSomethingElse()
            {
                lock (lockObj)
                {
                    System.Threading.Thread.Sleep(100);
                    Console.WriteLine("DoSomethingElse Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
                    DoSomething();
                }
            }
        }
    }
}

vesikuusi [25.09.2015 07:44:27]

#

groovyb kirjoitti:

No ei näkyvyyden hallinta huonoa suunnittelua ole. Protected, kuten internal ja muutkin access modifierit ovat käytännössä arkkitehtuuria rajaavia tekijöitä, joilla koodia pakotetaan tekemään samalla muotilla. Eli rajaamalla näkyvyyttä saadaan koodarit käyttämään vakiomenetelmiä ja patterneja. Asia erikseen on sitten kirjastot, joissa näkyvyyksillä halutaan rajata tapaa miten kirjastoa käytetään (vendor -kirjastot), kun alkuperäistä lähdekoodiakaan ei ole saatavilla. Jos itselle tai yksin koodaa, ei näillä asioilla ole juurikaan merkitystä. Jos käytetään ulkopuolista kirjastoa, on näkyvyys todennäköisesti määritetty niin että ainoastaan sallittuja asioita nähdään, ja pystyy koodissa räpläämään. Jos taas tehdään suurempaa projektia useammalla koodarilla, näkyvyydet määritellään jotta arkkitehtuuri pysyisi läjässä.

Kyllä, tämä on selvää. En siltikään osaa kuvitella protected-muuttujaa hyvänä suunnitelmana, oli sitten kyseessä kirjasto, itselle tehtävä koodi tai suurempi projekti. Periytyvä luokkamuuttuja, jota potentiaalisesti käytetään läpi koko hierarkian on pelottava ajatus. Tilannehan olisi jokaisen tämän hierarkian luokan kannalta sama kuin että se lukisi ja kirjoittaisi public-muuttujaa.

Lukkoesimerkkisi tosin näyttää fiksulta. Täytyisi tutkia sitä paremmalla ajalla ennen kuin viitsin kommentoida enempää.

feenix [25.09.2015 16:28:51]

#

jlaire kirjoitti:

Yhden luokan ja privaatin jäsenmuuttujan tapauksessa voi vakuuttua siitä, että koodi ei voi missään tapauksessa aiheutta deadlockia.

Mutta tässä kuka tahansa voi periä luokista ThreadObjA/TreadObjB, ja myöhemmin kaikki muutokset niihin voivat aiheuttaa ongelmia. Joutuu aina käymään koko luokkahierarkian läpi, kun haluaa varmistua koodin toimivuudesta.

Miksi periytetty lukko tuottaisi ongelmia? Sen kun voi lukita niin monta kertaa kuin haluaa samassa säikeessä. Ei mitään tarvetta käydä koko luokkahierarkiaa läpi, ellei sitten ole käytössä muitakin lukko-objekteja. Ja se ei taas korjaudu käyttämällä erillisiä lukkoja eri luokissa, ainoastaan monimutkaistuisi.

Tai miten antamalla konstruktorissa lukko-objekti eroaa yhtään mitenkään siitä, että on yksi lukko-objekti per instanssi, periytymisestä välittämättä? Kuitenkin se on se yksi objekti kaikille luokille, ellei muuta haluta. Ja jos halutaan, sen joutuu tekemään joka tapauksessa siellä perivässä luokassa uusiksi.

vesikuusi [25.09.2015 18:09:11]

#

Joo, tämä lukkoesimerkki on toimiva. Hauskaa että tällainen löytyi. Toisaalta tarve tällaiseen luokkien välillä jaettuun lukkoon voi syntyä käsittääkseni ainostaan siitä, että luokat jakavat jo valmiiksi jotakin muuta keskenään. Hieman ironista.

En kyllä osaa abstrahoida tätä lukkojuttua niin että keksisin jonkin toisen vastaavan esmerkin. Enkä keksi muutenkaan.

jlaire [25.09.2015 18:24:08]

#

Tästä esimerkistä on vähän vaikea keskustella, kun se on niin abstrakti. groovyb itsekin sanoi että teki sen näin "vain esimerkkinä protectedin käytöstä".

Kun jotain tietorakennetta pitää käsitellä monesta säikeestä, mutta toteutus ei ole säieturvallinen, minusta on järkevää enkapsuloida synkronisointi yhteen luokkaan ja tarjota turvallinen rajapinta. Toteutuksen yksityiskohtien, kuten synkronisointiin käytetyn muuttujan, paljastaminen "koska sitten niihin pääsee käsiksi" vaatii jonkin konkreettisen perustelun.

En löytänyt netistä mitään vakuuttavaa esimerkkiä, jossa jäsenmuuttujien näkyvyydeksi olisi järkevää asettaa protected. Näkisin mielelläni jonkun oikean esimerkin, vaikka sitten järkevän käyttötapauksen tälle lukkoperintähärpäkkeelle, jossa lukko suojelee jotain tietorakennetta.

feenix kirjoitti:

Miksi periytetty lukko tuottaisi ongelmia?

Samasta syystä kuin public.

feenix kirjoitti:

Ei mitään tarvetta käydä koko luokkahierarkiaa läpi, ellei sitten ole käytössä muitakin lukko-objekteja.

Jos haluat refaktoroida luokkaa ThreadObjA, on mahdollista että koko softa menee yhtäkkiä jumiin, koska jokin ThreadObjA:n perivä luokka kutsuu sen metodia ja oletti että niin voi tehdä lock(lockObj) {...} sisältä.

feenix kirjoitti:

Tai miten antamalla konstruktorissa lukko-objekti eroaa yhtään mitenkään siitä, että on yksi lukko-objekti per instanssi, periytymisestä välittämättä?

Jos se annetaan konstruktorissa, jäsenmuuttujan näkyvyyden voi asettaa privateksi.

vesikuusi [25.09.2015 18:24:37]

#

Okei, ymmärsin vasta nyt että tämä lukon periminen on vain tapa hankkia tietty lukko luokan käyttöön(?). Siis niinkuin yläpuolella keskustellaankin, lukon voisi antaa konstruktorissakin ja laittaa privaatiksi. Itse kyllä hankkisin lukon varmaankin rajapinnan takana, jostakin tehtaasta tms (nojoo, varmaan aika tapauskohtaisesti).

Lisäys:

jlaire kirjoitti:

En löytänyt netistä mitään vakuuttavaa esimerkkiä, jossa jäsenmuuttujien näkyvyydeksi olisi järkevää asettaa protected. Näkisin mielelläni jonkun oikean esimerkin --

Joo tätä just haen.

groovyb [25.09.2015 18:40:00]

#

jlaire kirjoitti:

Jos haluat refaktoroida luokkaa ThreadObjA, on mahdollista että koko softa menee yhtäkkiä jumiin, koska jokin ThreadObjA:n perivä luokka kutsuu sen metodia ja oletti että niin voi tehdä lock(lockObj) {...} sisältä.

tuo on nyt yleisesti puutaheinää. Refaktoroimalla saa mitä vaan rikki jos ei tiedä mitä tekee. Saatika että muutoksia tekisi säikeistetyssä ohjelmassa ilman että sulkee käynnissäolevat säikeet as in "Softa menisi jumiin".

protected toimii luokan ulkopuolella kuin private, se vaan näkyy läpi periytymishierarkian. Ajoin aika ison läjän testejä ajin eilen tuolle viritykselle, enkä saanut dead lockia aikaan.

public näkyy luokasta ulos. protected ei.

publicissa on riski että lukko voidaan avata luokan ulkopuolelta, protectedissa tätä riskiä ei ole.

ja viimeisin esimerkki poistaa deadlock mahdollisuuden instanssien välillä, missä lukko injektoidaan constructoriin, ja on eri instansseille yhteinen.

feenix kirjoitti:

Miksi periytetty lukko tuottaisi ongelmia? Sen kun voi lukita niin monta kertaa kuin haluaa samassa säikeessä. Ei mitään tarvetta käydä koko luokkahierarkiaa läpi, ellei sitten ole käytössä muitakin lukko-objekteja. Ja se ei taas korjaudu käyttämällä erillisiä lukkoja eri luokissa, ainoastaan monimutkaistuisi.

Tai miten antamalla konstruktorissa lukko-objekti eroaa yhtään mitenkään siitä, että on yksi lukko-objekti per instanssi, periytymisestä välittämättä? Kuitenkin se on se yksi objekti kaikille luokille, ellei muuta haluta. Ja jos halutaan, sen joutuu tekemään joka tapauksessa siellä perivässä luokassa uusiksi.

Juuri näin.

jlaire [25.09.2015 18:43:05]

#

groovyb kirjoitti:

tuo on nyt yleisesti puutaheinää. Refaktoroimalla saa mitä vaan rikki jos ei tiedä mitä tekee.

Jotkut patternit kuitenkin tekevät turvallisesta refaktoroimisesta helpompaa kuin toiset.

En ymmärrä, miten voit pitää protected jäsenmuuttujia yleisesti turhana ideana, mutta tässä lukkoesimerkissä et kuitenkaan suostu näkemään mitään ongelmia.

groovyb kirjoitti:

Ajoin aika ison läjän testejä ajin eilen tuolle viritykselle, enkä saanut dead lockia aikaan.

Et ilmeisesti ole lukenut yhtäkään viestiäni kunnolla.

https://www.ohjelmointiputka.net/keskustelu/28811-protected-jäsenmuuttuja-mitä-järkeä/sivu-1#v228601

Olen yrittänyt ymmärtää, mikä idea tässä rakenteessa on. Oikeassa koodissa luokissa oletettavasti olisi muutakin koodia kuin kaksi tyhjää metodia? Näkisin edelleen kovin mielelläni jonkun mielekkään käyttötapauksen.

Aivan yhtä voisin oksentaa tähän kasan koodia jossa on protected int x; ja perivä luokka joka tulostaa sen. Mutta topikkia lainaten, "mitä järkeä?".

Lisäys: Lue vaikka näitä, jos ei aihe ole tuttu.
http://programmers.stackexchange.com/a/75203
http://c2.com/cgi/wiki?ImplementationInheritanceIsEvil

groovyb [25.09.2015 19:11:42]

#

se että missä objekti sijaitsee, on sivuseikka, on se sitten private tai protected. Mielestäni et ymmärrä miten periytys toimii.

protected käyttäytyy ylläolevassa mallissa kuten private muuttujakin.
Kun instanssi sisältää muuttujan, on se vain ja ainoastaan luokan sisäinen oli se sitten periytetty protected, tai luokassa suoraan private.

Eli kun constructor ottaa vastaan objektin, on se aivan sama lyödäänkö se privateen, tai protected muuttujaan.

Se että jos välissä on yksi layer - tässä tapauksessa ThreadObjA, on myös sivuseikka. Se ei tee säikeistyksestä sen vaarallisempaa. Koodaajan tulee vain tietää seikka kun muokkaa ThreadObjA:ta. Vai tarkoitatko että sinun luokkasi eivät koskaan käytä mitään muuta luokkaa, ja niiden metodeita?

Saathan sinäkin mallillasi ohjelmasi kaatumaan jos kutsut metodissasi

var a = new jokuluokka;
a.Teejotain();

ja TeeJotain() metodi kaatuu muutoksiesi jälkeen.

Periytetyn luokan objektit ovat vain sen sisäisiä, ellei niille erikseen injektoi objektia luokan ulkopuolelta. Eri instanssien sisäiset objektit pohjaluokasta ovat siis erillisiä toisistaan. Eli ne eivät ole sama objekti, ellet sitten sitä tarkoituksella staattisena tee.

Lisäys:

Havainnollistava yksikkötesti asiasta:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace testNamespace
{
    [TestClass]
    public class objectTest
    {
        [TestMethod]
        public void AreNotSame()
        {
            B b1 = new B();
            B b2 = new B();

            Assert.AreNotSame(b1.GetBaseObj(), b2.GetBaseObj());
        }

        [TestMethod]
        public void AreSame()
        {
            object obj = new object();
            B b1 = new B(obj);
            B b2 = new B(obj);

            Assert.AreSame(b1.GetBaseObj(), b2.GetBaseObj());
        }
    }

    public class BaseClass
    {
        protected object base_object = new object();
    }

    public class A : BaseClass
    {
        public A() {}

        public A(object obj)
        {
            base.base_object = obj;
        }

        public object GetBaseObj()
        {
            return base.base_object;
        }
    }

    public class B : A
    {
        public B() {}

        public B(object obj) : base (obj){}
    }
}

jlaire [25.09.2015 19:31:45]

#

groovyb kirjoitti:

Mielestäni et ymmärrä miten periytys toimii.

Ymmärrän kyllä. Sinä sen sijaan et ymmärrä pointtiani.

groovyb kirjoitti:

Kun instanssi sisältää muuttujan, on se vain ja ainoastaan luokan sisäinen oli se sitten periytetty protected, tai luokassa suoraan private.

Jos tarkoitat, että jokaisella objektilla on omat instanssinsa ei-staattisista jäsenmuuttujista, niin totta kai. Suunnittelun kannalta protected ei kuitenkaan ole luokan sisäinen samalla tavalla kuin private on, koska siihen pääsee käsiksi myös perivistä luokista.

Toteutuksen kannalta kaikki LockBase:n, ThreadObjA:n, ThreadObjB:n ja muiden hierarkiaan mahdollisesti kuuluvien luokkien metodit käyttävät samaa lukkoa. Tarkoitan nyt siis esimerkiksi yhtä ThreadObjB:n luokan instanssia ja sen base-luokkia, en erillisiä instansseja.

Näiden luokkien kesken on erittäin tight coupling, ja jos lapsiluokat kutsuvat base-luokkien metodeja, voi base-luokan refaktorointi aiheuttaa helposti ongelmia. Se, sisältääkö jokin ThreadObjA:n metodi lock(lockObj)-rakenteen, ei ole enää ThreadObjA:n sisäinen toteutusyksityiskohta, vaan vaikuttaa siihen, missä tilanteissa lapsiluokka voi kutsua tätä metodia.

Ymmärrätkö pointtini? Olen sen jo aika monta kertaa selittänyt, mutta takerrut joka kerta tuon triviaalin esimerkkisi yksityiskohtiin.

groovyb kirjoitti:

Eli kun constructor ottaa vastaan objektin, on se aivan sama lyödäänkö se privateen, tai protected muuttujaan.

Koodin toimivuuden kannalta public/protected/private ovat totta kai sama asia. Suunnittelun kannalta eivät ole.

groovyb [25.09.2015 19:34:35]

#

no itse näen tässä samaa ongelmaa selityksesi kanssa. Jos refaktoroit, niin voit rikkoa asioita. Tottakai voit. Ihan sama miten olet tehnyt.

Mutta deadlockin kanssa asialla ei ole mitään tekemistä, onko protected vai private.

Kun kyse on lukko -objektista, ei sitä refaktoroida eikä kuulu sen piiriin. Sen tarkoitus on vain ja ainoastaan toimia lukkona metodeissa.

Jos protected jäsenmuuttuja tekisi jotain muuta, olisi sillä luonnollisesti suurempi vaikutus.

jlaire kirjoitti:

Näiden luokkien kesken on erittäin tight coupling, ja jos lapsiluokat kutsuvat base-luokkien metodeja, voi base-luokan refaktorointi aiheuttaa helposti ongelmia

Ja mitä tulee base -luokan metodeihin, on se nyt aika normaali tapa koodata.
ei base -luokat ole vain propertyille. Pohjaluokassa on ne metodit, joita halutaan myös lapsissa yhteisesti käyttää. Ei base -luokkien ole tarkoitus olla pelkästään itsenäisiä kokonaisuuksia, vaikka sellaisena voivat toimiakin.

jlaire [25.09.2015 19:40:19]

#

Oletko käyttänyt LockBase:a tai vastaavaa joskus oikeassa koodissa?

Jos et ja tämä on alusta asti ollut täysin turhaa pelleilyä, luovutan.

Tässä oma kontribuutioni vesikuusi:n kysymykseen: protected jäsenmuuttujia voi käyttää esimerkiksi näin:

public class IntBase {
    protected int x = 5;
}

public class IntA : IntBase {
    public void DoSomething() { x++; }
}

public class IntB : IntA {
    public void DoSomethingElse() { x *= 2; }
}
public class ListBase {
    protected List<int> list = new List<int>();
}

public class ListA : ListBase {
    public void DoSomething() { list.add(1); }
}

public class ListB : ListA {
    public void DoSomethingElse() { list.add(2); }
}

Kätevää, eikö? Jos koodin toiminta on epäselvää, voin ilomielin pastea 200 riviä yksikkötestejä ja esimerkkiohjelmia, jotka eivät tee mitään järkevää. Älä kuitenkaan kysy käytännön sovelluksista, jätän ne kotitehtäväksi.

groovyb [25.09.2015 19:47:40]

#

Aijai, jos nyt muutat DoSomethingElse metodissa listan nulliksi, DoSomething kaatuu. Tämä pattern ei taida olla turvassa refaktoroinnilta?

Ymmärrän mitä tarkoitat jlaire, mutta ei se tarkoita etteikö tuota samaa mallia voi käyttää vaikka luokan sisäisessä lokituksessa (esim että joka luokassa generoidaan oman filunsa, johon lokitetaan krääsää), ja pohjaluokan sisältämä lukko tarjoaa ko. filuun kirjoittamisen lukon, sekä luo itse lokitusobjektin jota sitten käytellään lapsien metodeissa. Tässä tapauksessa ei tarvita edes injektoitua lukkoa, vaan käy ihan tuo alkuperäinen malli.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta