In my opinion, putting multiple classes definitions in one unit is very senseful when the are correlated, so that at usage stage one just need to include one single unit.
On the other hand this might result in very large units. Someone might not find this a problem, but I don't like so much (just taste I think).
Without knowing your code, this usually is a sign that you have either to many interdependent classes, or your classes are getting way to big. If it's the former, than maybe you should consider making some of the private fields you need to access public, or put them in a common base class and make them protected, or make a distinction between a working unit and a publishing unit, where the working unit has accessors to those mebers, but this is only used within the classes that need it:
Unit that will be included to use TMyClass externally:
unit MyUnit;
{$mode ObjFPC}{$H+}
interface
uses
MyUnit.Internal;
type
TMyClass = MyUnit.Internal.TMyClass; // Publish TMyClass
implementation
end.
Unit that defines TMyClass internally:
unit MyUnit.Internal;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils;
type
{ TMyClass }
TMyClass = class
private
FFoo: Integer;
public
procedure PrintFoo;
end;
{ TMyClassPublisher }
TMyClassPublisher = class helper for TMyClass
private
function GetFoo: Integer;
procedure SetFoo(AValue: Integer);
public
property Foo: Integer read GetFoo write SetFoo;
end;
implementation
{ TMyClass }
procedure TMyClass.PrintFoo;
begin
WriteLn(FFoo);
end;
{ TMyClassPublisher }
function TMyClassPublisher.GetFoo: Integer;
begin
Result := Self.FFoo;
end;
procedure TMyClassPublisher.SetFoo(AValue: Integer);
begin
Self.FFoo := AValue;
end;
end.
Using the internal unit:
unit FactoryUnit;
{$mode ObjFPC}{$H+}
interface
uses
MyUnit.Internal;
function CreateClass(Foo: Integer): TMyClass;
implementation
function CreateClass(Foo: Integer): TMyClass;
begin
Result := TMyClass.Create;
Result.Foo := Foo; // Access to the member that is only published in MyUnit.Internal
end;
end.
Using the published unit:
program Project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
Classes, MyUnit, FactoryUnit;
var
c: TMyClass;
begin
c := CreateClass(42);
C.PrintFoo;
c.Foo := 32; // Doesn't work because type helper is not published by MyUnit
ReadLn;
end.
Splitting up between internal and external code is somethign I do quite often. Often it's not necessarily with a type helper to accessing private fields, because I rarely have the situation that I need to access another classes private fields, but this is usually for helper functions that the outside don't need to see (e.g. different functions for Windows and Unix to ensure cross compatibility).
If it's the latter and you have few classes which get really big, then this is usually a sign that you do to much stuff within one class. It is often helpful to put code in helper functions which are not directly members of the class, but just plain old functions, or to split up big classes in multiple classes.
That said, there is one big problem and this is if you need cyclic references, then you must put them all in one class. And I also have some projects where I have units with like 1000-1500 lines of code because of this. But thats the exception and I think this can still be managable thanks to IDE support