Indirekter Attributzugriff

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Liebes Forum,

mal wieder ne OOP Frage von mir: ich hab eine Klasse, die als eine Art Vermittler zwischen zwei anderen Klassen (nennen wir sie a und b) fungiert. Nun benötige ich in einer dritten Klasse z den Wert eines Attributes aus Klasse a, für die der Vermittler die Schnittstelle bildet. Ist es nun häßlich/schlechter Stil, auf das gewünschte Attribut indirekt zuzugreifen? Also zu sagen:
Vermittler.a.attribut
Anstelle dessen könnte ich natürlich in der anderen Klasse, welche den Attributwert benötigt, eine Instanz von a erzeugen und dann direkt auf das Attribut zugreifen. Aber das erscheint mir auch häßlich, denn es würde meine COdestruktur mit dem Vermittler über den Haufen werfen.
Habe mal irgendwo gelesen, dass man so Kettenzugriffe vermeiden soll, weil das keine Sau mehr durchblickt. Aber einfach neue Instanzen rauszuballern, wenn man gerade irgendein Attribut braucht, erscheint mir auch nicht schön. Was also tun? :K
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das was dagegen spricht nennt sich Demeters Law. Google mal, dann wird klar warum man das nicht tun soll.

Für deinen Fall könnte also einfach ein property z auf dem Vermittler die Lösung sein.

Wenn ich allerdings höre, das du ja einfach auch eine neue Instanz erzeugen könntest, dann verstehe entweder ich etwas nicht, oder es handelt sich eigentlich um Daten, die nicht in eine Instanz gehören.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@T.T. Kreischwurst: kannst Du etwas konkreter werden. Vielleicht Deinen Code zeigen. Die Rolle des `Vermittlers` wird mir nicht ganz klar. Für was brauchst Du den? Was sind a und b?

Wenn man es streng nach Gesetzt sieht, dann ist der Vermittler entweder nicht richtig zugeschnitten, wird gar nicht gebraucht, oder b übernimmt Aufgaben, die eigentlich der Vermittler machen müßte, oder a übernimmt Aufgaben, die eigentlich der Vermittler machen müßte.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Danke für die Antworten und entschuldigt den fehlenden Code, war spät gestern :-D
Erläuterung: das ist das Modul, das das ganze GUI Zeugs enthält und das Steuerungsmodul, welches das Programm startet und auch das GUI Zeug erstmalig aufruft. Mein Demeter Problem (danke, Deets, SO hieß das…) befindet sich an folgenden Stellen:
Button-Klasse UpdateJob. Hier erfolgt der böse Zugriff
Klasse TextTableMediator – wie der Name schon sagt, die Vermmittlerklasse. Sie enthält eine Referenz auf:
Klasse Textitem – die Klasse mit der Methode, die ich in UpdateJob aufrufen will.

Ich weiß inzwischen schon gar nicht mehr, wie oft ich dieses Modul schon umgebaut habe, weil das Design nachweislich oder meiner unqualifizierten Meinung nach unschön war… Vielleicht finde ich ja irgendwann mal eine Version, die ich stehen lassen kann. Das ganze Programm würde ich dann mal separat zur Begutachtung posten, wenn es einigermaßen fertig ist.

GUI-Modul:

Code: Alles auswählen

import tkinter as tk
import sys
import datetime
import AV3_Dialogs as dial
import AV3_DB

class MainMenu():
    
    def __init__(self, root, controller, db_path):
        
        self.controller     = controller
        self.root           = root
        self.db             = AV3_DB.Database(db_path)
        self.joblist        = self.db.jobs_select_all()
        self.current_date   = datetime.date.today()
        self.date_displayed = tk.StringVar()
        self.container      = None
        self.date_label     = None
        self.timetable      = None
        
        self.set_date(self.current_date)
        self.set_frame()
        
    def set_frame(self):
        
        ''' Set a frame (with the current date displayed on top) for all buttons to be packed in.'''
        
        self.container = tk.Frame(
            self.root,
            bd=2,
            relief=tk.GROOVE)
        self.container.grid(
            row     = 0,
            column  = 0,
            padx    = int(self.root.screen_width*0.03),
            pady    = int(self.root.screen_height*0.04),
            sticky  = tk.NW)
        self.date_label = tk.Label(
            self.container,
            textvariable=self.date_displayed,
            font=('Segoe UI', 14, 'bold'))
        self.date_label.pack()
        
        # Initiate the timetable grid for today
        self.timetable = TextTable_Mediator(self.root, self.current_date, self.joblist)
        self.site_buttons()
        
    def set_date(self, date):
        
        ''' Take any date(time) object and convert it to a certain format, which will be displayed at the head of the main menu. '''
        
        formatted_date = datetime.datetime.strftime(date, '%A, %d. %B %Y')
        self.date_displayed.set(formatted_date)
        
    def site_buttons(self):
        
        ''' Create object of the button classes. Place these buttons inside the menu frame. '''
        
        # Exit button is some kind of exception: the callback method is stored inside the controller attribute. It's not really worth creating a separate class.
        exit_program = tk.Button(
            self.container,
            text="Beenden",
            command=self.controller.close_app)
        exit_program.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
        
        # PDF Creator
        print_pdf = PrintPdfButton(self.container)
        print_pdf.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
        
        # Change the current processing date (this will load previously saved jobs to the timetable grid).
        load_date = LoadDate(self.container, self.root, self)
        load_date.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
            
        # Add a job to d&d menu
        add_job = AddJob(self.container, self.root, self.db, self.timetable)
        add_job.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
            
        # Save currently deployed jobs
        save_jobs = SaveJobs(self.container, self.root, self.db, self.timetable)
        save_jobs.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
            
        # Remove a job
        delete_job = RemoveJob(self.container, self.root, self.db, self.timetable)
        delete_job.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
            
        # Modify a job's name
        update_job = UpdateJob(self.container, self.root, self.db, self.timetable)
        update_job.pack(
            padx=int(self.root.screen_width*0.02),
            pady=int(self.root.screen_height*0.03))
            
class PrintPdfButton(tk.Button):
    
    def __init__(self, container):
        
        tk.Button.__init__(
            self,
            master=container,
            text="PDF erzeugen",
            command=self.execute)
            
    def execute(self):
        
        ''' Dummy class yet - intended to create some "PDF_Printer" object later. '''
         
        print('1000x geklickt, 1000x ist nix passiert.')
        
class LoadDate(tk.Button):
    
    def __init__(self, container, root, menu):
        
        self.root   = root
        self.menu   = menu
        tk.Button.__init__(
            self,
            master=container,
            text="Datum laden",
            command=self.execute)
            
    def execute(self):
        
        ''' Loads a user provided date for display and further processing. '''
         
        new_date = dial.ChangeDate(self.root)
        # Now set the new date at two places:
        # On the class attribute (as datetime object) for further processing, and:
        # On the instance attribute of MainMenu (as string/stringvar) for display only.
        self.menu.current_date = new_date.date
        self.menu.set_date(new_date.date)
        
        # Replace the timetable object since we have a new day
        self.menu.timetable = TextTable_Mediator(self.root, new_date.date, self.menu.joblist)

class AddJob(tk.Button):
    
    def __init__(self, container, root, db, timetable):
        
        tk.Button.__init__(self,
            master=container,
            text="Job hinzufügen",
            command=self.execute)
        self.root       = root
        self.db         = db
        self.timetable  = timetable
        self.job        = None
        
    def execute(self):
        
        ''' Requests a jobname. '''
        
        jobname = dial.GetJobName(self.root)
        self.job = jobname.job
        insert_flag = self.db.insert_job(self.job)
        # The function will return either the ID or None if the given job already exists
        if insert_flag:
            print('Job ', self.job, ' has been  successfully inserted at index', insert_flag)
            self.timetable.list_job(self.job)
        else:
            tk.messagebox.showerror("Fehler beim Erstellen des Jobs", "Es existiert bereits ein Job unter dem angegebenen Namen.\nBitte wählen Sie einen anderen!")
            
class RemoveJob(tk.Button):
    
    def __init__(self, container, root, db, timetable):
        
        tk.Button.__init__(self,
            master=container,
            text="Job löschen",
            command=self.execute)
        self.root       = root
        self.db         = db
        self.timetable  = timetable
        
    def execute(self):
        
        ''' Remove a certain job from the joblist, the timetable (if present) and the database. '''
        
        dialog = dial.DeleteJob(self.root, self.db)
        self.timetable.canvas.delete(dialog.delJob)

class SaveJobs(tk.Button):
    
    def __init__(self, container, root, db, timetable):
        
        tk.Button.__init__(self,
            master=container,
            text="Jobs speichern",
            command=self.execute)
        self.root       = root
        self.db         = db
        self.timetable  = timetable
        
    def execute(self):
        
        ''' Call a method in canvas class that returns info about all dropped jobs. Then pass them to the db object for saving. '''
        
        print(self.timetable.job_info)
        
class UpdateJob(tk.Button):
    
    def __init__(self, container, root, db, timetable):
        
        '''This class lets the user change a job's name without having to delete it - and with that all the dates this particular job was performed. '''
        
        tk.Button.__init__(self,
            master=container,
            text="Jobname anpassen",
            command=self.execute)
        self.root       = root
        self.db         = db
        self.timetable  = timetable
        
    def execute(self):
        
        ''' Call dialog: select a job, then update the stuff on the canvas if possible. If not, delete and rebuild the canvas. Ugly - avoid that!'''

        dialog = dial.RenameJob(self.root, self.db)
        self.timetable.active_textitem.change_text(dialog.old_name, dialog.new_name) # Indirekter Zugriff - ist das scheiße???
  
class Textitem():
    
    def __init__(self, canvas, coords, tags, **kwargs):
        
        self.canvas     = canvas
        self.coords     = coords
        self.tags       = tags
        self.textitem   = None
        self.highlighted = None
        
        # The first of these methods is mandatory and executed in any case:
        # Place the item; then edit its appearance (optional, if necessary).
        self.place_item()
        self.edit_item(**kwargs)
            
    def place_item(self):
        
        assert type(self.tags) is tuple, 'tags need to be of type tuple. Use one-tuples for single tags: (tag1,)'
        
        self.textitem = self.canvas.create_text(self.coords[0],
                                self.coords[1],
                                text=self.tags[0],
                                tags=self.tags)

    def edit_item(self, **kwargs):
        
        try:
            for arg, value in kwargs.items():
                self.canvas.itemconfig(self.textitem, **kwargs)
        except tk.TclError as e:
            print(e, ' - argument is not valid for function tk.Canvas.itemconfig()! See tkinter docu for allowed options.')

    def change_text(self, name, value):
        
        itemID = self.canvas.find_withtag(name)
        self.canvas.itemconfig(itemID, text=value)
        
    def fill(self, event=None):
        
        # Size for background is the text's bbox and an additional padding of 10.
        size = self.canvas.bbox(tk.CURRENT)
        size = (size[0] - 10, size[1] - 10, size[2] + 10, size[3] + 10)
        
        self.canvas.itemconfig(tk.CURRENT, activefill='white')
        rect = self.canvas.create_rectangle(size, fill='blue', width=0)
        self.canvas.tag_lower(rect, 'list')
        self.highlighted = rect
        
    def delete_backgr(self, event=None):
        
        self.canvas.delete(self.highlighted)
        
class Timetable():
    
    def __init__(self, canvas, date):

        self.canvas     = canvas
        self.date       = date
        self.increment  = datetime.timedelta(seconds=900)           # 15 minutes 
        self.width      = 0
        self.height     = 0
        self.coords     = []
        self.info       = []
        self.set_size()
    
    def set_size(self):
        
        self.canvas.update()        # Necessary because winfo_width() will otherwise always return 1 - no idea why...
        self.width = self.canvas.winfo_width() / 6  # 2/3 are preserved for the rectagnles, the rest remains free for the jobtiles 
        self.height = self.canvas.winfo_height() / 16.5 # 15 Rows, a label and some padding
        self.draw_grid()
    
    def draw_grid(self):
        
        ''' Generate a 4x14 table, where each field represents 15 minutes of time. The respective timespan is saved together with the ID of the connected field. '''
        
        start_time = datetime.time(hour=7)
        start_time = datetime.datetime.combine(self.date, start_time)
        
        y1 = self.height     # Initial value is one tile's distance from top border (left clear for label)
        y2 = y1 + self.height
        
        # outer loop: build rows
        for y in range(14):
            
            x1 = self.width * 1.5    # Initial value is one tile's distance from left border (left clear for label)
            x2 = x1 + self.width
            
            #inner loop: add four columns per row
            for x in range(4):
                
                rect = self.canvas.create_rectangle(x1,y1,x2,y2)

                # increment the counters
                x1 += self.width
                x2 += self.width
                end_time = start_time + self.increment
                
                #Create a text item displaying the end time in the background of the current rectangle
                rect_bbox = self.canvas.bbox(rect)
                x_coord = rect_bbox[2] - (rect_bbox[2] - rect_bbox[0]) / 2
                y_coord = rect_bbox[3] - (rect_bbox[3] - rect_bbox[1]) / 2
                text = 'bis ' + str(end_time.time())
                Textitem(self.canvas, (x_coord, y_coord), (text, 'time'), fill='#AFABAB')
                
                # save the id, start and end times and increment the time counter
                self.info.append((rect, start_time, end_time))
                start_time = end_time
                
            y1 += self.height
            y2 += self.height
            
    def check_overlap(self, text_coords):
        
        for rect in self.info:
            rect_coords = self.canvas.coords(rect[0])
            if rect_coords[0] <= text_coords[0] <= rect_coords[2] and \
               rect_coords[1] <= text_coords[1] <= rect_coords[3]:
                return rect[0]

class TextTable_Mediator():
    
    def __init__(self, root, date, jobs):
        
        self.date = date
        self.jobs = jobs
        self.active_textitem = None
        self.canvas = tk.Canvas(root,
                            width=int(root.screen_width*0.7),
                            height=int(root.screen_height*0.85),
                            bd=3,
                            relief=tk.GROOVE)
        self.canvas.grid(
                    row     = 0,
                    column  = 1,
                    padx    = int(root.screen_width*0.03),
                    pady    = int(root.screen_height*0.04))
        self.timetable = Timetable(self.canvas, self.date)
        for x in self.jobs.values():
            self.list_job(x)
        self.event_bindings()

    def list_job(self, job):
        
        ''' Adds a textitem to the joblist at the left of the timetable. The item is placed on the first piece of free space available, meaning that free gaps will be filled before a job is attached to the end of the list. '''
        
        x_coord = self.canvas.winfo_width() * 0.08
        y_coord = self.canvas.winfo_height() * 0.05
        
        while self.canvas.find_overlapping(
                x_coord,
                y_coord,
                x_coord+1,
                y_coord+1):
            y_coord += self.canvas.winfo_height() * 0.04
        item = Textitem(self.canvas, (x_coord, y_coord), (job, 'list'))
        self.active_textitem = item
        
    def event_bindings(self):
        
        ''' Event bindings for Drag and Drop, rightclicking and maybe other events are defined here. '''
        
        self.canvas.tag_bind('list', '<ButtonPress-1>', self.on_B1_press)
        self.canvas.tag_bind('list', '<B1-Motion>', self.on_csr_move)
        self.canvas.tag_bind('list', '<ButtonRelease-1>', self.on_B1_rel)
        self.canvas.tag_bind('list','<Enter>', self.active_textitem.fill)
        self.canvas.tag_bind('list','<Leave>', self.active_textitem.delete_backgr)

    def on_B1_press(self, event=None):

        ''' Called when user clicks on a job to d&d it. Gets required info and creates a new Texitem object, which should should have been done directly in the event binding optimally. '''

        coords = (event.x, event.y)
        name = self.canvas.itemcget(tk.CURRENT, 'text')

        self.active_textitem = Textitem(self.canvas, coords, (name, 'isDouble'))

    def on_csr_move(self, event):

        ''' Move the dragged textitem around - if it is hovered over a rectangle, highlight it. '''

        # Move it
        double = self.canvas.find_withtag('isDouble')
        self.canvas.coords(double, event.x, event.y)

        # Clear last matched rectangle, if exisiting
        last_hovered = self.canvas.find_withtag('last_hovered')
        if last_hovered:
            self.canvas.itemconfig(last_hovered, outline='black', width=1)
            self.canvas.dtag(last_hovered, 'last_hovered')

        # Find a matching rectangle and highlight it, set a 'current' flag for this rect
        match = self.timetable.check_overlap(self.canvas.bbox(double))
        if match:
            self.canvas.itemconfig(match, outline='#215EBB', width=3)
            self.canvas.addtag_withtag('last_hovered', match)

    def on_B1_rel(self, event=None):

        ''' Called when user drops the job somewhere, that is on release of mouse button 1. '''
        
        match = self.timetable.check_overlap((event.x, event.y))

        if match:   # If Job is dropped outside grid, check_overlap returns none
            # prepare coords and name so we can display the job in the background
            match_coords = self.canvas.coords(match)
            text_coords = (int(match_coords[0]+20), int(match_coords[1]+10))
            name = self.canvas.gettags(self.active_textitem.textitem)[0]    # get the double item's name (=first tag)
            name = (name,)
            self.active_textitem = Textitem(self.canvas,
                                            text_coords,
                                            name)
            self.active_textitem.edit_item(
                                width=int(self.timetable.width-5),
                                font=('Segoe UI', 10),
                                fill='white')
            self.canvas.itemconfig(match, fill='#008000')

            # Delete the background time, this got somewhat ugly
            for items in self.canvas.find_withtag('time'):
                if self.timetable.check_overlap(self.canvas.coords(items)) == match:
                    self.canvas.delete(items)
            
        double = self.canvas.find_withtag('isDouble')
        self.canvas.delete(double)
Steuerungsmodul:
[Codebox=python file=Unbenannt.py]
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import AV3_CONF
import AV3_GUI
import AV3_Dialogs as Dial
import platform, locale
import tkinter as tk
import datetime

class Launcher():

def __init__(self):

''' This class makes sure alle prerequisites, mainly loading or requesting cfg data, are met for the GUI to start correctly. '''

self.config = AV3_CONF.Config()
self.config_values = {}
self.db_path = None
self.root = None
self.setup()

def setup(self):

''' This controls the correct setup of the main window and its most central module, the main menu. '''

self.set_locale()

self.root = MainWindow()
self.root.protocol('WM_DELETE_WINDOW', self.close_app)

# Get the cfg stuff
if self.config.configfile_exists():
# read_cfg returns 1 if all is OK - we must not proceed unless some checks are OK.
if not self.read_cfg():
return
else:
self.get_cfg_data()

self.root.deiconify()
AV3_GUI.MainMenu(self.root, self, self.config_values['db'])
self.root.mainloop()

def read_cfg(self):

''' Read values from cfg file and check the password '''

try:
self.config_values = self.config.read_confvalue()
except KeyError as e:
tk.messagebox.showerror("Fehler in settings.ini", "Die Konfigurationsdatei 'settings.ini' wurde unzulässig modifiziert.\n Korrigieren Sie den Fehler oder löschen Sie settings.ini - alle Einstellungen gehen dabei aber verloren!\n\nSiehe Konsolen-Log für Details.")
print('The following value in the cfg file was corrupt: ', e)
return
pw_win = Dial.CheckPassword(self.root, self.config_values['pw'])
try:
pw_win.password
print('Setup complete, building GUI...')
except AttributeError: # If the user skips the password check, there will be no such attribute. This must be handled and is used here to exit the program.
self.root.destroy()
print('Password entry aborted, exiting program.')
return

# If nothing went wrong:
return 1

def get_cfg_data(self):

''' As we have no cfg file, request the necessary data from the user via dialogs. '''
self.db_path = Dial.ask_db_path()
self.db_path += '/data.db'
pw_win = Dial.RequestNewPassword(self.root)
self.config_values = {'db':self.db_path, 'pw':pw_win.password} # Dict is used instead of passing the values directly as parms because of better expandability later.
self.config.set_confvalues(init_values=self.config_values)

def set_locale(self):

''' Set locale depending on OS. Tkinter Widgets tend to be mixed English and German on Mac OS X systems (in my experience) '''

if platform.system() == 'Windows':
locale.setlocale(locale.LC_ALL, 'deu_deu')
else:
locale.setlocale(locale.LC_ALL, 'de_DE')

def close_app(self):

''' User must confirm before closing the application. All changes to config file are saved here; therefore any changes will be lost if the app crashes or is closed via terminal kill. '''

yesno = tk.messagebox.askyesno("Beenden", "Programm wirklich beenden?")
if yesno:
self.config.set_confvalues(add_value = str(datetime.datetime.now()))
self.config.write_settings()
self.root.destroy()

class MainWindow(tk.Tk):

def __init__(self):
tk.Tk.__init__(self)
self.screen_width = self.winfo_screenwidth()
self.screen_height = self.winfo_screenheight()
self.setup_window()

def setup_window(self):
self.title("Arbeits-Management BETA")
self.geometry('%dx%d+0+0' %(self.screen_width, self.screen_height))
self.withdraw() # Root window should not be visible until starting routines like PW check etc. are complete.

if __name__ == '__main__':
x = Launcher()
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@T.T. Kreischwurst: Deine ganzen Button-Klassen sind überflüssig. Nur wegen einer Execute-Methode braucht man keine eigene Klasse. Zum konkreten Problem, würde ich eine change_active_text-Methode schreiben.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

@Sirius: das mit den Klassen begreife ich einfach nicht. Zuerst (hier: viewtopic.php?f=9&t=39496&hilit=Zeitmanagement) hieß es, ich brauche mehr Klassen. Jetzt sinds zu viele - ich komm da nicht mehr mit :K
Wie meinst du das mit der Methode? In der Vermittlerklasse, oder?
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist hier schon sehr oft gesagt worden : Klassen die nur aus einem konstruktor und einer Methode bestehen sind meistens überflüssig. Und das gilt hier auch für deine Button-Klassen.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

:D OK OK ihr habt ja Recht... Ich führe die Button klassen zu einer einzigen namens ButtonFunctions() zusammen, die die ganzen bisherigen execute() Methoden und als Attribute die ganzen Instanzen, die die Methoden brauchen (wie z. B. DB, etc) enthält. Die site_buttons() Methode bleibt, aber dort werden die Buttons platziert anstatt Objekte erzeugt.
Aber bzgl. der Methode, die Sirius erwähnt hat, bin ich mir immer ich nicht sicher: gibt die im Vermittler den Aufruf nur an die Textitem Klasse weiter?
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@T.T. Kreischwurst: welche fachliche Berechtigung hat denn eine Klasse, die nur Knopf-Methoden enthält? Was sich bewährt hat, ist eine Klasse pro Fenster, weil das eine in sich abgeschlossene Einheit ist; die erzeugt alle GUI-Elemente und verknüpft die Aktionen mit Eventmethoden. Außerdem erhält sie eine Instanz der Geschäftslogik-Klasse, die z.B. die eigentlichen Daten bereitstellt. Jede Eventmethode ruft exakt eine Methode der Geschäftslogik auf und sorgt, bei Änderung der Daten, für die aktualisierung der Anzeige.

Wenn Du jetzt noch ein komplizierteres Widget wie eine Tabelle hast, dann kann das eine weitere Klasse sein, die aber wieder nur für die Darstellung verantwortlich ist. Entsprechende Daten bekommt sie ausschließlich von der Fensterklasse, muß also von der Geschäftslogik nichts wissen.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

:?: Aber dann hab ich ja wieder ne Gottklasse, die fast alles tut... Das gesamte Modul wäre dann - streng genommen - eine Klasse, da es sich um ein Fenster handelt. Und die Geschäftslogik (Datenbank und cfg Datei Anbindung) ist da gar nicht drin.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist keine Gott-Klasse, sondern eine Klasse, die die GUI-Methoden enthält, weil diese eine sinnvolle Einheit bilden. An welcher Stelle würdest Du denn die GUI-Klasse aufteilen wollen?
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Naja, ich hab ja die MainMenu Klasse, die einen Frame und einige Buttons darin bündelt. Dann die komplexere Canvas mit den Items darauf, die eine eigene Klasse haben. Das finde ich schon sinnvoll, weil graphische Einheiten, die recht eng miteinander interagieren, so in einer Klasse gebündelt sind. Die ButtonFunctions Klasse hätte die Berechtigung, dass sie die ausführenden Teile der Buttons von den graphischen (wie Platzierung) trennt und diese bündelt
Tut mir Leid, wenn das renitent oder begriffstutzig rüber kommt, ich tu mir nur echt hart damit zu verstehen, wie man denn nun Klassen RICHTIG definiert.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Keine Sorge. Deine aufrichtiges Bemühen ist sichtbar. Es ist auch sicherlich eine der schwierigeren Probleme eine Intuition für diese Dinge zu bekommen.

Der Grund warum dein Vorschlag noch wirklich Anklang findet ist einfach: er stellt nur eine syntaktische, aber nicht wirklich logische Trennung dar. Zwar ist es unrealistisch zu glauben man kann alles völlig losgelöst von anderen Dingen machen. Man will ja etwas schreiben was wirklich was tut. Aber das Ideal ist das schon.

Deine angekündigte Hilfsklasse hat viel intimes wissen von der GUI, muss viel Zustand übergeben bekommen und wird immer angefasst werden müssen wenn du etwas Neues einführst.

Stattdessen ist es besser in gut separierbare teile zu investieren. Zb statt einen Dialog zu machen der einen Job in der DB löscht, und danach aus dem Dialog die id dieses Jobs zu pulen um irgendwas in der gui zu machen solltest du dein Model so gestalten, das es beim löschen eines Jobs ein Signal Sendet. In dieses Signal kann sich deine GUI auch an 10 Stellen einhängen, und dann ist es egal, ob und wie der Dialog sich verändert. Oder es andere Pfade zum löschen eines Jobs gibt.

Dann hast du Dinge wo du immer erwartest, das zwei Sachen zusammen passieren. Das ist zb mit current_date so. Das setzt du, und dann rufst du erstmal set_date auf. Mach currebt_date ein property & setzt die visuelle Repräsentation sofort im setter der property. Damit hast du ein klareres Interface und vergisst so etwas nicht.
T.T. Kreischwurst
User
Beiträge: 52
Registriert: Dienstag 2. Februar 2016, 10:56

Keine Sorge. Deine aufrichtiges Bemühen ist sichtbar. Es ist auch sicherlich eine der schwierigeren Probleme eine Intuition für diese Dinge zu bekommen.
Danke :)
Stattdessen ist es besser in gut separierbare teile zu investieren. Zb statt einen Dialog zu machen der einen Job in der DB löscht, und danach aus dem Dialog die id dieses Jobs zu pulen um irgendwas in der gui zu machen solltest du dein Model so gestalten, das es beim löschen eines Jobs ein Signal Sendet. In dieses Signal kann sich deine GUI auch an 10 Stellen einhängen, und dann ist es egal, ob und wie der Dialog sich verändert. Oder es andere Pfade zum löschen eines Jobs gibt.
:!: Ah, jetzt beginne ich zu verstehen: optimalerweise sollte eine Klasse so eigenständig sein, dass sie ohne die Existenz anderer Klassen lauffähig wäre, oder? Dass meine Klassen zu spezifisch und nicht allgemein/abstrakt/unabhängig/wie auch immer sind, hab ich irgendwie geahnt, konnte es aber bislang nicht wirklich greifen. Daher ist das Beispiel nicht schlecht, denn es zeigt gut, wo mein Problem liegt. Ich weiß zwar, dass Klassen abstrakter sein sollten, aber ich wüsste z.B. im genannten Fall nicht, wie das in der Praxis aussehen sollte.
Wie würde das Signal aussehen - ich würde hier als Signal die Eingabe des Nutzers nehmen, aber wo führe ich dann sinnvollerweise das eigentliche Löschen, also die DB Aktion aus? Den Schritt aus der Theorie in die Praxis kann ich mir nicht wirklich vorstellen. Bzw. es ist mir bei meinen mittlerweile 5 Anläufen, in denen ich das Programm jeweils mehr oder weniger komplett neu geschrieben habe, nie zufriedenstellend gelungen. Ehrlich gesagt geht mir auch langsam die Puste aus, ich kann die Materie allmählich nicht mehr sehen :x
Mach currebt_date ein property & setzt die visuelle Repräsentation sofort im setter der property. Damit hast du ein klareres Interface und vergisst so etwas nicht.
Das verstehe ich nicht ganz, mit Properties hab ich bislang nie gearbeitet - komplettes Neuland
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Verzeih das ich mich erst jetzt melde.

Signale sind einfach ein klassisches observer pattern. Kann man auf viele Arten implementieren, da findet sich bestimmt was auf active State oder SO.

Du das mit dem property ist ganz simpel:

Code: Alles auswählen

@property
def current_date(self):
     return self._current_date

@current_date.setter
def current_date(self, value):
      self._current_date = value
      self.set_date(value)
Antworten