Pomoc w dziedziczeniu

0

Witam,

Nie mam zbyt wiele poważnego doświadczenia w programowaniu dlatego chciałem zapytać kolegów/koleżanek bardziej doświadczonych żeby na przyszłość lepiej programować i nie wdrażać złych nawyków w swoim życiu.

Wracając do tematu. Napisałem pewien kod w WPF, który ma za zadanie sprawdzić textbox login, wywołać animację (to raczej nie istotne w tym temacie) i następnie zareagować na wpisanie hasła. Mniej więcej wygląda to jak w google, czyli najpierw wpisujemy login i klikamy w celu sprawdzenia poprawności wpisania danych i dopiero wtedy wyświetla nam się textbox do wpisania hasła.

Kod działa, ale nie wiem czy jest on napisany poprawnie (chodzi tu o konwencje, sposób napisania, użycia poprawnych metod). Użyłem zmiennej static i gdzieś słyszałem, że lepiej jest ich nie używać i np. użyć do tego konstruktora. Wklejam kod i proszę o jakiś feedback, a szczególnie wyjaśnienie na temat 'statica', czy go używać kiedy się chce, czy bardziej ograniczać.

Klasa ConnectionLogin

class ConnectionLogin : Connection
    {
        private static string _login = "null";
        public virtual string loginName
        {
            get { return _login; }
        }
        
        public async Task TryLogginAsync(string textFromTextBox)
        {
            if(CheckForConnectionInternet()) // Funckja statyczna spr internet z klasy Connection.
            {
                if(!textFromTextBox.Contains('.') && textFromTextBox != "admin")
                {
                    MessageBox.Show("Zły login.","Błąd uwierzytelniania");
                    return;
                }
                _login = textFromTextBox;

                await Task.Factory.StartNew(() =>
                {
                    AnimeLogin anime = new AnimeLogin();
                    anime.RunningAnime();
                });
            }
        }
        
    }

oraz Klasa po niej dziedzicząca ConnectionPassword:

class ConnectionPassword : ConnectionLogin, IConnectSQL
    {
        public string Query { get; set; }

        public override string loginName => base.loginName;
        public void Login(string textFromTextBox)
        {
            if (CheckForConnectionInternet())
            {
                Query = "SELECT * FROM Users WHERE Login='" + loginName + "' AND Password='" + textFromTextBox + "';";
                const string connectInfo = "String z danymi.";
                MySqlConnection ConnectSQL = new MySqlConnection(connectInfo);
                try
                {
                    ConnectSQL.Open();
                    MySqlCommand cmd = new MySqlCommand(Query, ConnectSQL);
                    MySqlDataReader reader = cmd.ExecuteReader();
                    DataTable dt = new DataTable();
                    dt.Load(reader);

                    if (dt.Rows.Count < 1)
                    {
                        MessageBox.Show("Nie znaleziono takiego użytkownika.", "Błąd uwierzytelniania konta.");
                        return;
                    }
//Dalszy kod, który uruchomi nowe okno aplikacji.
                }
                catch
                {
                    MessageBox.Show("Błąd połączenia z bazą danych. Prawdopodobnie przekroczono limit czasu.", "Błąd połączenia.");
                }
            }
        }
    }
5

tak na szybko - dziedziczenie generalnie stosuje się na zasadzie od ogólu do szczegółu (zwierze -> pies) (mebel-> szafa)
tutaj - przynajmniej ja - nie widzę związku dla ConnectionLogin > ConnectionPassword.
Pierwsze pytanie - czemu dziedziczenie jest w tą stronę a nie ConnectionPassword ? Jedyna odpowiedź bo kolejnośc działań, ale to jest bardzo złe, bo w ogóle nie ma sensu.

Zamiast tego proponuję zastosować kompozycję

class LoginSystem
{
/* jedna uwaga, bardziej pro byłoby to zrobić na interfejsach*/
    private readonly ConnectionLogin _conLogin;
    private readonly ConnectionPassword _conPass;

    public LoginSystem(ConnectionLogin  cLogin, ConnectionPassword  cPass) 
    {
        _conLogin = cLogin;
        _conPass= cPass;
    }
/*
tu już logika systemu do logowania z wykorzystaniem obiektów składowych
*/

}

3

Tak na szybko:

  • zmienna statyczna służy do tego, żeby można było na niej operować na rzecz klasy, a nie obiektu. Czy to jest coś, co chciałeś tutaj osiągnąć? Czy może chcesz gdzieś przechować aktualnie zalogowanego użytkownika?

Zapytania SQL. Tutaj kilka problemów:

  1. Mieszasz odpowiedzialności
  2. Narażasz się na SQL Injection (doczytaj to hasło)

Jeśli nie używasz żadnego ORMa, to stwórz sobie repozytorium (doczytaj hasło: "wzorzec repozytorium") albo chociaż jakąś utilsową klasę, która będzie wywoływać SQLe.
W tym momencie metoda Login jest odpowiedzialna za kilka rzeczy: komunikację z bazą danych, sprawdzenie poprawności logowania, zalogowanie użytkownika i nie wiadomo co jeszcze.
To powinno być rozbite. To powinno wyglądać mniej więcej tak:


enum LoginResult
{
    Success,
    UserNotExists,
    BadPassword
}

class UserManagement
{
    async Task<LoginResult> LoginUser(string name, string pass)
    {
        if(! await db.UserExists(name))
            return LoginResult.UserNotExists;

        var passFromDb = await db.GetUserPassword(name);
        bool passIsValid = passValidator.Validate(pass, passFromDb);
        
        return passIsValid ? LoginResult.Success : LoginResult.BadPassword;

    }
}

Teraz metoda LoginUser oddelegowuje prace do innych metod i klas. Obiekt db to jakiś obiekt odpowiedzialny za komunikację z bazą danych (względnie repozytorium). Obiekt passValidator to jakiś obiekt sprawdzający, czy podane hasła są jednakowe. Pamiętaj, że w bazie nie przechowuje się haseł czystym tekstem, tylko są one (powinny być) w jakiś sposób szyfrowane. Zazwyczaj robi się hash hasła (funckja skrótu) z jakimś "salt'em" (czyli jakąś dodatkową informacją, np: hasloDoBazy = hash(haslo + aktualna_data))

Dlatego też passvalidator wydaje się potrzebny. Dalej otwiera Ci to drogę do różnych metod kodowania hasła. Wystarczy zmienić implementację passValidator i nie tykasz w ogóle samego logowania.

No, to tak na szybko.

Aaaa, a co do SQLa. Poczytaj o SQL Injection. Parametry do zapytania powinny być przekazywane w odpowiedni sposób. W ADO w klasie Command masz coś takiego jak Parameters. Czyli nie:

string sql = "SELECT * FROM users where username = '" + userName.Text"'"

Tylko coś w rodzaju (pseudokod):

string sql = "SELECT * FROM users where username = %userName%";
SqlCommand cmd = new SqlCommand(sql);
cmd.Parameters.Add("%userName%", userName.Text);
0

Rozumiem. Czyli całą operacja logowania i wpisywania hasła najlepiej wprowadzić w jednej klasie?

Narażasz się na SQL Injection (doczytaj to hasło)

Tak, słyszałem o tym. Na razie za bardzo się nie skupiałem na zapytaniu SQL, ale dziękuję za informację.

1
Bialas95 napisał(a):

Rozumiem. Czyli całą operacja logowania i wpisywania hasła najlepiej wprowadzić w jednej klasie?

  1. programowanie obiektowe nie polega na tym, że zamykamy w klasie "wszystko co wiemy". Klasy mają zakresy odpowiedzialności. Więc jak mówisz o jakiejś jednej klasie, to zgadnij jakie emocje we mnie to budzi.
  2. bardzo stare (tak się wtedy myślało o zagadnieniach - ale to bardzo stare) i nowe głupawe poradniki skupiały/skupiają się na dziedziczeniu.
    Słusznie @jakubek zwraca uwagę, aby nie dziedziczyć głupio i używać kompozycji. Jedyne uprawnione dziedziczenie to owoc->jabłko

Powiedziałbym wręcz, że samo twoje pytanie jest źle skonstruowane, bo ma sens "jak dziedziczyć" a nie "jak dobrze programować obiektowo". Dziedziczenie jest tylko jednym z elementów, i nowsze spojrzenia podkreślają, wcale nie najważniejszym.

0

A co do hermetyzacji. Czy w takim razie powinienem używać częściej modyfikatora internal (czy raczej jest on rzadko używany) albo już ustawiać zmienną public. Szczerze rzadko widzę w czyimś kodzie ten modyfikator. Nie mówię już o private bo wiem, że najlepiej tak konstruować żeby zmiennych,metod publicznych było jak najmniej na zew. klasy. Chodzi mi oczywiście tak z doświadczenia waszego, bo wiadomo, że według poradników to wszystko jest super i wszystko jest warte używania i zazwyczaj opisane jest ogólnie.

1

A co do hermetyzacji. Czy w takim razie powinienem używać częściej modyfikatora internal

tu potrzeba wyobraźni i doświadczenia - internal ma sens, gdy apka składa się z wielu assembly (dll-ek) czy chcesz aby dana klasa była dostepna spoza assembly czy nie. Taki widoczny minus internali, testowanie (unittesty to zazwyczaj osobnej assembly) ale da się to obejść adnotacją
**[assembly: InternalsVisibleTo("gdzie.to.ma.byc.widoczne")]. **
Także tutaj odpowiedź nie jest jednoznaczna.

Ale (taki przykłąd z głowy na szybko) np weźmy pod uwagę coś takiego jak mappery, czyli jest jakaś konwersja. I teraz patrzysz się czy ta konwersja jest sprawą wewnętrzą (bo dzieje się tylko w tej częsci apki, zamkniętej w dll-ce), czy np chcemy aby do tej konwersji każdy mial dostep.

0

W takim razie dziękuję wszystkim wam za informację i cenne uwagi. Trzeba się teraz wziąć za poprawę mojego kodu i zwracać uwagę żeby za dużo nie dziedziczyć. Na szczęście na razie kodu napisałem w tej apce mało :) Pozdrawiam.

1
Bialas95 napisał(a):

W takim razie dziękuję wszystkim wam za informację i cenne uwagi. Trzeba się teraz wziąć za poprawę mojego kodu i zwracać uwagę żeby za dużo nie dziedziczyć. Na szczęście na razie kodu napisałem w tej apce mało :) Pozdrawiam.

composition over/vs inheritance - możesz na to popatrzeć. Programowanie obiektowe to praca na obiektach.
Pomyśl tak, samochód to kompozycja wielu obiektów, dzieki czemu latwo to podmienić - dziedziczenie niejako usztywnia relacje. Ale czasem jest to ok.
Tutaj nie ma sztywnych reguł, tu trzeba czytać (kod) pisać, sprawdzać - szukać, patrzeć jak coś jest zrobione. Przy dziedziczeniu dobre jest pytanie - skąd taka relacja, co za nią stoi.
Np odwołując się do samochodu to dziedziczenie może być faktem, ze iles modeli ma taki sam silnik, albo podwozie.

Mam nadzieję, że czujesz róznice - jeśli mamy takie same podwodzie (dziedziczmymy) to nie może być samochód większy, szerszy, ale może mieć róznego rodzaju dzwi, światla, fotele, bo tu komponujemy ten samochód

0

Mam nadzieję, że czujesz róznice - jeśli mamy takie same podwodzie (dziedziczmymy) to nie może być samochód większy, szerszy, ale może mieć róznego rodzaju dzwi, światla, fotele, bo tu komponujemy ten samochód

Szczerze to używałem dziedziczenia tutaj bo po pierwsze chciałem mniej linijek kodu, a po drugie bo wiedziałem, że będę używał większość zmiennych i metod z danej klasy. Tworzenie wszędzie nowych instancji itp. wydawało mi się mniej 'ładne' dla oka. Prawda taka, że w pracy jednak nauczę się najwięcej, aczkolwiek jak zacznę pracować to wolał bym już coś więcej umieć.

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