Ho un editor di file. Legge da un file, crea un oggetto con i dati letti e può quindi scrivere quei dati in uno stesso (o altro) file.
Ogni volta che un utente apre un file, viene creata una nuova scheda e a essa viene assegnata un'istanza privata dell'oggetto, che la contiene nella scheda.
L'oggetto stesso è complesso ed è composto da altri oggetti. Ogni tipo personalizzato ha metodi Write()
e Read()
che estraggono e salvano i dati da e nel file. Qualcosa del genere:
TopObject a0
int a
int b
string c
CustomObjectMid d
int e
bool f
List<CustomObjectMid2> g
CustomObjectMid3 h
CustomObjectBottom i
long j
ulong k
Dictionary<string, int> l
byte m
L'oggetto reale è molto più grande e ha più profondità (livelli) ad esso.
TopObject
, CustomObjectMid
, CustomObjectMid2
, CustomObjectMid3
e CustomObjectBottom
hanno tutti i metodi Write()
e Read()
che popolano tutti i tipi standard con i dati del file. Non contengono altri metodi. Non hanno nemmeno costruttori (diversi da quelli predefiniti).
L'unico scopo di questo oggetto è conservare le informazioni. È essenzialmente una struttura. Questo è in realtà scritto come una DLL che lo scopo di consentire agli utenti di dare un senso a quel particolare formato di file (leggere da esso e scrivere ad esso). Quindi il codice attuale non è nel mio programma, ma fa parte di una libreria. Pertanto, vorrei mantenere la libreria relativamente "neutra" - non voglio implementare il codice specifico per l'editor nella libreria.
Il modo in cui questo oggetto è implementato è che tutto è pubblico e posso accedere a qualsiasi cosa in a0.h.i.j
modo. Tendo a passare parti rilevanti degli oggetti a pezzi di programma che li usano, quindi non devo fare riferimento a tutto dall'alto (a0).
Altre parti del programma visualizzano i dati all'utente e traducono le modifiche apportate dall'utente all'oggetto. Eseguono inoltre i controlli necessari per mantenere l'integrità dei dati (impedire il passaggio di caratteri per interi, ecc.)
La mia domanda è questo buon design? Ho deciso di non usare getter e setter. Sarebbe solo gonfiare il codice senza motivo (almeno non riesco a vederne uno).
Tutto ciò che viene detto, per qualche motivo questo mi sembra un brutto codice (non so perché), ma non vedo alcun altro modo di implementarlo senza fare eccessivo, dato il suo ruolo nel programma.
EDIT: Ecco il codice completo della classe root. Tutte le altre classi hanno la stessa struttura:
namespace SaveManipulator.GameData
{
[Serializable]
public class CustomFile
{
//version = 7
public Value<uint> saveFileVersion;
//ecd
public ECD ecd;
//food
public Food food;
//drink
public Drink drink;
//inventory
public Stack[] inventory;
//selectedSlot
public Value<int> selectedSlot;
//bag
public Stack[] bag;
//craftedList
public HashSet<string> craftedList;
//spawnPoints
public List<Vector3Di> spawnPoints;
//SpawnKey
public Value<long> SpawnKey;
//notSaved = true
public Value<bool> randomBoolean;
//notSaved = 0
public Value<short> randomShort;
//bLoaded
public Value<bool> bLoaded;
//lastPosition
public Vector3Di lastPosition;
//id
public Value<int> id;
//droppedPosition
public Vector3Di droppedPosition;
//kills
public Value<int> kills;
//fKills
public Value<int> fKills;
//deaths
public Value<int> deaths;
//score
public Value<int> score;
//equipment
public Equipment equipment;
//unlockedList
public List<string> unlockedList;
//notSaved = 1
public Value<ushort> randomUShort;
//marker
public Vector3Di marker;
//favorites
public Equipment favorites;
//experience
public Value<uint> experience;
//level
public Value<int> level;
//bCrouched
public Value<bool> bCrouched;
//cdata
public Cdata cdata;
//favoriteList
public List<string> favoriteList;
//J
public Skills skills;
//totalItems
public Value<uint> totalItems;
//distance
public Value<float> distance;
//life
public Value<float> life;
//waypoints
public Waypoints waypoints;
//skillPoints
public Value<int> skillPoints;
//quests
public Quests quests;
//deathTime
public Value<int> deathTime;
//currentL
public Value<float> currentL;
//bDead
public Value<bool> bDead;
public CustomFile Clone()
{
Stream stream = new FileStream("CustomFile ", FileMode.Create, FileAccess.Write, FileShare.None);
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Close();
stream = new FileStream("CustomFile", FileMode.Open, FileAccess.Read, FileShare.None);
PlayerDataFile clone = (PlayerDataFile)formatter.Deserialize(stream);
stream.Close();
File.Delete("CustomFile");
return clone;
}
public void Read(string path)
{
BinaryReader reader = new BinaryReader(new FileStream(path, FileMode.Open));
if (reader.ReadChar() == 'd' && reader.ReadChar() == 't' && reader.ReadChar() == 's' &&
reader.ReadChar() == 'TopObject a0
int a
int b
string c
CustomObjectMid d
int e
bool f
List<CustomObjectMid2> g
CustomObjectMid3 h
CustomObjectBottom i
long j
ulong k
Dictionary<string, int> l
byte m
')
{
saveFileVersion = new Value<uint>((uint)reader.ReadByte());
ecd = new ECD();
ecd.Read(reader);
food = new Food();
food.Read(reader);
drink = new Drink();
drink.Read(reader);
inventory = Stack.Read(reader);
selectedSlot = new Value<int>((int)reader.ReadByte());
bag = Stack.Read(reader);
//num
int craftedListLength = (int)reader.ReadUInt16();
craftedList = new HashSet<string>();
for (int i = 0; i < craftedListLength; i++)
{
craftedList.Add(reader.ReadString());
}
//b
byte spawnPointsCount = reader.ReadByte();
spawnPoints = new List<Vector3Di>();
for (int j = 0; j < (int)spawnPointsCount; j++)
{
Vector3Di spawnPoint = new Vector3Di();
spawnPoint.x = new Value<int>(reader.ReadInt32());
spawnPoint.y = new Value<int>(reader.ReadInt32());
spawnPoint.z = new Value<int>(reader.ReadInt32());
spawnPoints.Add(spawnPoint);
}
spawnKey = new Value<long>(reader.ReadInt64());
randomBoolean = new Value<bool>(reader.ReadBoolean());
randomShort = new Value<short>(reader.ReadInt16());
bLoaded = new Value<bool>(reader.ReadBoolean());
lastPosition = new Vector3Di();
lastPosition.x = new Value<int>(reader.ReadInt32());
lastPosition.y = new Value<int>(reader.ReadInt32());
lastPosition.z = new Value<int>(reader.ReadInt32());
lastPosition.heading = new Value<float>(reader.ReadSingle());
id = new Value<int>(reader.ReadInt32());
droppedPosition = new Vector3Di();
droppedPosition.x = new Value<int>(reader.ReadInt32());
droppedPosition.y = new Value<int>(reader.ReadInt32());
droppedPosition.z = new Value<int>(reader.ReadInt32());
kills = new Value<int>(reader.ReadInt32());
fKills = new Value<int>(reader.ReadInt32());
deaths = new Value<int>(reader.ReadInt32());
score = new Value<int>(reader.ReadInt32());
equipment = Equipment.Read(reader);
//num
int count = (int)reader.ReadUInt16();
unlockedList = new List<string>();
for (int k = 0; k < count ; k++)
{
unlockedList .Add(reader.ReadString());
}
randomUShort = new Value<ushort>(reader.ReadUInt16());
marker = new Vector3Di();
marker.x = new Value<int>(reader.ReadInt32());
marker.y = new Value<int>(reader.ReadInt32());
marker.z = new Value<int>(reader.ReadInt32());
favorites = Equipment.Read(reader);
experience = new Value<uint>(reader.ReadUInt32());
level = new Value<int>(reader.ReadInt32());
bCrouched = new Value<bool>(reader.ReadBoolean());
cdata = new Cdata();
cdata.Read(reader);
//num
int favoriteListSize = (int)reader.ReadUInt16();
favoriteList = new List<string>();
for (int l = 0; l < favoriteListSize; l++)
{
favoriteList.Add(reader.ReadString());
}
//num2
int size = (int)reader.ReadUInt32();
skills = new Skills();
skills.Read(reader);
totalItems = new Value<uint>(reader.ReadUInt32());
distance = new Value<float>(reader.ReadSingle());
life = new Value<float>(reader.ReadSingle());
waypoints = new Waypoints();
waypoints.Read(reader);
skillPoints = new Value<int>(reader.ReadInt32());
quests = new Quests();
quests.Read(reader);
deathTime = new Value<int>(reader.ReadInt32());
currentL = new Value<float>(reader.ReadSingle());
bDead = new Value<bool>(reader.ReadBoolean());
//irelevant bytes
reader.ReadByte();
reader.ReadBoolean();
reader.Close();
}
else
{
throw new IOException("Save file corrupted!");
}
}
public void Write(string path)
{
BinaryWriter writer = new BinaryWriter(new FileStream(path, FileMode.Create));
writer.Write('d');
writer.Write('t');
writer.Write('s');
writer.Write((byte)0);
writer.Write((byte)saveFileVersion.Get());
ecd.Write(writer);
food.Write(writer);
drink.Write(writer);
Stack.WriteItemStack(writer, inventory);
writer.Write((byte)selectedSlot.Get());
Stack.WriteItemStack(writer, bag);
writer.Write((ushort)craftedList.Count);
HashSet<string>.Enumerator enumerator = craftedList.GetEnumerator();
while (enumerator.MoveNext())
{
writer.Write(enumerator.Current);
}
writer.Write((byte)spawnPoints.Count);
for (int i = 0; i < spawnPoints.Count; i++)
{
writer.Write(spawnPoints[i].x.Get());
writer.Write(spawnPoints[i].y.Get());
writer.Write(spawnPoints[i].z.Get());
}
writer.Write(selectedSpawnKey.Get());
writer.Write(randomBoolean.Get());
writer.Write(randomShort.Get());
writer.Write(bLoaded.Get());
writer.Write((int)lastPosition.x.Get());
writer.Write((int)lastPosition.y.Get());
writer.Write((int)lastPosition.z.Get());
writer.Write(lastPosition.heading.Get());
writer.Write(id.Get());
writer.Write(droppedPosition.x.Get());
writer.Write(droppedPosition.y.Get());
writer.Write(droppedPosition.z.Get());
writer.Write(kills.Get());
writer.Write(zKills.Get());
writer.Write(deaths.Get());
writer.Write(score.Get());
equipment.Write(writer);
writer.Write((ushort)unlockedList.Count);
List<string>.Enumerator enumerator2 = unlockedList.GetEnumerator();
while (enumerator2.MoveNext())
{
writer.Write(enumerator2.Current);
}
writer.Write(randomUShort.Get());
writer.Write(marker.x.Get());
writer.Write(marker.y.Get());
writer.Write(marker.z.Get());
favorites.Write(writer);
writer.Write(experience.Get());
writer.Write(level.Get());
writer.Write(bCrouched.Get());
cdata.Write(writer);
writer.Write((ushort)favoriteList.Count);
List<string>.Enumerator enumerator3 = favoriteList.GetEnumerator();
while (enumerator3.MoveNext())
{
writer.Write(enumerator3.Current);
}
skills.Write(writer);
writer.Write(totalItems.Get());
writer.Write(distance.Get());
writer.Write(life.Get());
waypoints.Write(writer);
writer.Write(skillPoints.Get());
quests.Write(writer);
writer.Write(deathTime.Get());
writer.Write(currentL.Get());
writer.Write(bDead.Get());
writer.Write((byte)88);
writer.Write(true);
writer.Close();
}
public CustomFile(string path)
{
Read(path);
}
}
}
Dato che il codice è per lo più copiato dalla sorgente del gioco, l'ho offuscato un po ', ecco perché alcuni nomi possono sembrare vaghi. Anche Value<Type>
è una classe wrapper usata per passare attorno ai tipi di valore pur mantenendo il loro riferimento senza doverlo pensare esplicitamente. In questo modo l'utente può modificare i bit del file più facilmente. Non vedo davvero come potrei salvare parzialmente il file. I file di salvataggio sono in genere da 4kB a (la mia stima) 10kB. Non sono così grandi.