Slicer script repository: module_name.logic vs slicer.module_name.widgetRepresentation().self().logic() vs slicer.modules.logic()

Slicer API explanation:

Hello all I have been trying to understand the Slicer API and I am having a lot of trouble understanding exactly how the ecosystem works.

I want to specifically understand how the logic aspect of modules works in Slicer.
In the script repository, I have seen the logic() incorporated in 3 different ways:

Example 1:
SegmentStatistics is often imported and accessed like this:

import SegmentStatistics
segStatLogic = SegmentStatistics.SegmentStatisticsLogic().Compute

However, I have seen other modules logic method accessed this way:
Example 2:

slicer.modules.markups.logic().ExportControlPointsToCSV(markupsNode, "/path/to/MyControlPoints.csv")

so how come I cannot access ComputeStatistics() like this with the code below?

slicer.modules.segmentstatistics.logic()

I can also see that i can access ComputeStatistics() if I access it via widgetRepresentation
Example 3:

slicer.modules.segmentstatistics.widgetRepresentation().self().logic.computeStatistics()

Overall what is the difference between the 3 ways? Which one the correct way and the theory behind the code. I understand that widgetRepresentation handles the gui and by qt, but I cant wrap my head behind Slicer design pattern especially with all the vtk/qt bindings.
Finally, whats the intuition behind accessing the self() in widget-representation from Slicer? Are the modules not already instantiated when slicer is loaded? I have seen this often used with segment editor example to access the different modes like pen, eraser and etc.

Thank you
Tas