Wzorzec repozytorium. Kilka niepewności. Co zamiast

Wzorzec repozytorium. Kilka niepewności. Co zamiast
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

Czytam różne rzeczy na temat tego, jak wzorzec repozytorium powinien wyglądać.

Np. powinien posiadać interfejs generyczny: IRepository<TEntity>
Potem Generyczną klasę implementującą ten interfejs: Repository<TEntity>: IRepository<TEntity>
No i Konkretne klasy, np: ClientRepository: Repository<Client>

Inni mówią, że nie wzorzec nie musi być generyczny.
Kiedy lepiej stosować generyczny, a kiedy nie? Zaraz ktoś się pewnie przyczepi, że lepiej w ogóle nie stosować repozytorium. OK. Ale co zamiast? Do tej pory radziłem sobie z tym problemem nieco inaczej, ale chciałbym spróbować jakiegoś nowego podejścia. Nie używam żadnego ORMa.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0

Konkretne repozytoria mogą mieć wspólne bazowe repozytorium generyczne, które realizuje wspólne zadania. Jest to dobre podejście gdy repozytorium jest wraperem na ORMa, a ten posiada generyczne API. Jeśli repozytorium zawiera jakieś ręczne budowanie zapytań, to niekoniecznie jest sens wydzielać coś bazowego, a zwłaszcza generycznego.
Błędem jest wypuszczanie repozytorium generycznego do logiki biznesowej, bo ideą repozytorium jest bycie specjalizowaną kolekcją. A generyczny jest przeciwieństwem specjalizowanego.

Ogólnie czego nie zrobisz, to i tak na 95% nie wyjdzie Ci repozytorium tylko zwykłe DAO.

edytowany 1x, ostatnio: somekind
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0
somekind napisał(a):

Konkretne repozytoria mogą mieć wspólne bazowe repozytorium generyczne, które realizuje wspólne zadania. Jest to dobre podejście gdy repozytorium jest wraperem na ORMa, a ten posiada generyczne API.

Czyli coś takiego:

Kopiuj
public class Repository<TEntity>
{
    public void Add(TEntity ent);
    public void Delete(TEntity ent);
    public void Update(TEntity ent);
    public IEnumerable<TEntity> GetAll();
}

public class ClientRepository: Repository<Client>
{
    public IEnumerable<Client> GetClientsWithBigPenis();
}

zgadza się?

Jeśli repozytorium zawiera jakieś ręczne budowanie zapytań, to niekoniecznie jest sens wydzielać coś bazowego, a zwłaszcza >generycznego.

Ale czemu nie? Przecież w repozytorium mam jakiś data kontekst, który może budować pytania dynamicznie za pomocą np. jakiś atrybutów. Coś w deseń:

Kopiuj
public class DataContext<TEntity>
{
  public void AddEntity(TEntity ent)
  {
    string tableName = ent.GetType().GetAttributeValue("TableName"); //pseudokod
  }
}

Czyli w klasie Client muszę mieć tylko atrybut TableName (nie wiem, czy to dobre). A nazwami pól będą po prostu nazwy właściwości. Tak chyba działają ORMy, nie?

Błędem jest wypuszczanie repozytorium generycznego do logiki biznesowej, bo ideą repozytorium jest bycie specjalizowaną kolekcją. A generyczny jest przeciwieństwem specjalizowanego.

Ogólnie czego nie zrobisz, to i tak na 95% nie wyjdzie Ci repozytorium tylko zwykłe DAO.

A czy repozytorium nie jest takim przykładem DAO?

S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
0

W sumie to ja nie wiem czym się różni DAO od Repository
Według mnie to całkiem sensownie jest zrobione w Springu Data JPA gdzie jest generyczny interface podstawowy który jest w locie implementowany przez framework i podstawowe operacje są "współdzielone" i jak trzeba to robisz dodatkowe metody w swoich repositoriach


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0
Juhas napisał(a):

zgadza się?

No mniej więcej... Tylko ja bym nie robił public IEnumerable<TEntity> GetAll(); bo to nigdy nie jest potrzebne, a łatwo tym zabić przypadkowo aplikację.
Lepiej byłoby: public IEnumerable<TEntity> GetFiltered(Expression<Func<TEntity, bool>> filter);

Czyli w klasie Client muszę mieć tylko atrybut TableName (nie wiem, czy to dobre). A nazwami pól będą po prostu nazwy właściwości. Tak chyba działają ORMy, nie?

No tak, ale dynamiczne budowanie zapytań na podstawie struktury klasy to nie powinna być odpowiedzialność repozytorium.

A czy repozytorium nie jest takim przykładem DAO?

Nie, to coś pomiędzy DAO a logiką biznesową.

scibi92 napisał(a):

W sumie to ja nie wiem czym się różni DAO od Repository

Znaczy gardzisz nawet nomenklaturą i wzorcami stworzonymi przez ekspertów Javy. Słusznie! ;)

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

źródło: https://martinfowler.com/eaaCatalog/repository.html

I dodatkowo: http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/

edytowany 1x, ostatnio: somekind
S9
Nie gardzę. tylko są to nieraz bardzo podobne sforłumowania i terminy. poza tym ja nie uczę się Javy, uczę programować :)
somekind
Znowu nie załapałeś ironii. ;) Ale nieważne - jeśli faktycznie uczysz się programowania, powinieneś się uczyć także prawidłowego używania wzorców.
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

A przy okazji. Czy łączenie data mappera z domeną jest zła? Np. coś takiego:

Kopiuj
public class Client
{
  public string FirstName { get; private set; }
  public string LastName { get; private set; }

  public Client(string firstName, string lastName)
  {

  }

  public static Client FromDB(IDataReader dataRow)
  {
    return new Client(
        dataRow["fName"].ToString(),
        dataRow["lName"].ToString());
  }
}

Czy to już łamie Single Responsibility?

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
1

Łamie SRP i uzależnia warstwę logiki biznesowej od szczegółów technologii używanej w celu dostępu do danych. To się nazywa active record i moim zdaniem jest okropne.

JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

Czyli w grę wchodzi tylko repozytorium + DataMapper? Czy jest jeszcze jakieś inne POPRAWNE* rozwiązanie?

*poprawne oznacza niełamiące podstawowych zasad.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
1

Pod Repozytorium nie musisz mieć DataMappera, to może być Table Data Gateway albo Data Access Object. No i repozytorium być nie musi w ogóle.

grzesiek51114
grzesiek51114
@Juhas, @somekind: kontynuujcie ten wątek, bo jest naprawdę ciekawy.
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

Czyli równie dobrze mogę sobie napisać klasę w stylu:

Kopiuj
public class DataProvider
{
  public Client GetClientById(UInt64 id)
  {
    //uproszczony pseudokod
    string sql = "SELECT * FROM clients WHERE id = " + id.ToString();
    return (Client)dataReader.ExecuteQuery(sql);   
  }

  public Invoice GetInvoiceById(UInt64 id)
  {
    string sql = ... itd
  }
}

I to wystarczy? Zrobiłbym z tego singletona, dał jeszcze jeden poziom abstrakcji, żeby uniezależnić się od DBMS i wszyscy będą zadowoleni?

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
3

@Juhas: że tak się wtrącę: jak już musisz robić singletona to skorzystaj z kontenera IoC i zarejestruj w nim klasę jako singleton. Mniej pisania, lepsza organizacja, oszczędność czasu.

edytowany 1x, ostatnio: grzesiek51114
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

@grzesiek51114: czemu kontener IoC? Z tego, co rozumiem, to będzie po prostu jakaś fabryka, tak? Coś w stylu:

Kopiuj
Singletons.Get<DataProvider>();

zamiast:

Kopiuj
DataProvider.Instance;

Poza tym nie ma, z tego co wiem, żadnego takiego wbudowanego mechanizmu, więc musiałbym albo jakiś pobrać, albo napisać własną fabrykę. Czemu w tym wypadku pierwszy sposób miałby być lepszy?

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
1

Nie musisz bawić się w tworzenie brzydkich staticów. Poza tym dochodzą wszystkie zalety IoC związane chociażby z Dependency Injection: https://stackoverflow.com/questions/871405/why-do-i-need-an-ioc-container-as-opposed-to-straightforward-di-code Czyli generalnie niebawienie się konstruktorami, co jest strasznie irytujące kiedy musiałbyś schodkowo przekazywać parametry przez X klas. Tak masz IoC i jedyne co musisz zrobić do wyciągnąć z niego obiekt, a parametry w konstruktorze takiego obiektu uzupełnią się same. Nie uzależniasz wtedy od siebie aż tak bardzo poszczególnych klas.

Ot tworzysz zwykłą klasę, a resztą zajmuje się IoC. To jest wygodne po prostu.

Zresztą przykład masz chociażby w tym linku, który Ci podesłałem:

Wow, can't believe that Joel would favor this:

Kopiuj
var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

over this:

Kopiuj
var svc = IoC.Resolve<IShippingService>();

PS: Co do funkcjonalności out of box: masz przecież Unity od MS i Ninjecta czy wiele innych. Starczy dodać do projektu przez nuGet'a, a sama konfiguracja tego to naprawdę nic skomplikowanego.

edytowany 7x, ostatnio: grzesiek51114
caer
jak dla mnie to pierwsze jest dużo lepsze, problem tylko z brakiem leniwej ewaluacji, ale to można rozwiązać wybierając lepszy język programowania
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0

@Juhas: tylko czemu chcesz robić z tego singletona?

JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

Bo nie widzę zastosowania dla kilku instancji takiej klasy. A singleton będzie bardziej użyteczny moim zdaniem niż klasa statyczna.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
3

Yyy... lekko w szoku jestem.
Singletony tworzy się, gdy jest potrzeba posiadania jednego obiektu danego typu, a nie profilaktycznie dla wszystkich klas. Jaki będziesz miał zysk ze zrobienia singletona, a nie normalnego tworzenia instancji? W czym konkretnie one przeszkadzają? Singleton, ze względu na długi czas życia, to proszenie się o kłopoty w przypadku operacji na bazie danych.

john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
2
Juhas napisał(a):

Bo nie widzę zastosowania dla kilku instancji takiej klasy. A singleton będzie bardziej użyteczny moim zdaniem niż klasa statyczna.

Skoro nie potrzebujesz kilku instancji danej klasy to ich po prostu nie twórz. W entry poincie tworzysz sobie jedną instancję i ją po prostu przekazujesz do obiektów, które jej potrzebują. Nie ma potrzeby przyzywania tego potwora pt. "singleton pattern" :P

JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

@somekind: nie chcę tworzyć profilaktycznie dla wszystkich klas :| Chcę stworzyć dla tej jednej mapującej dane z bazy danych.
@john_klamka: co za różnica, czy stworzę sobie singletona, czy obiekt w entry poincie, który będę przekazywał dalej? Dla mnie to drugie wyjście jest głupie. Czemu miałoby być lepsze?

john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
2
Juhas napisał(a):

@somekind: nie chcę tworzyć profilaktycznie dla wszystkich klas :| Chcę stworzyć dla tej jednej mapującej dane z bazy danych.
@john_klamka: co za różnica, czy stworzę sobie singletona, czy obiekt w entry poincie, który będę przekazywał dalej? Dla mnie to drugie wyjście jest głupie. Czemu miałoby być lepsze?

jak to:

Kopiuj
public class Singleton
{
    public string DoSomething() { //... }
}

może być "głupsze" od tego:

Kopiuj
public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }

   public string DoSomething() { //... }
}
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0

Porównałeś klasę nie będącą thread safe z singletonem będącym therad safe. Gdzie tu sens? Poza tym przy pobieraniu danych z bazy potrzebuję tylko jednego połączenia z bazą danych. A więc jednej instancji klasy. Zgadza się? Do takich rzeczy przecież jest stworzony singleton.

john_klamka
  • Rejestracja:prawie 9 lat
  • Ostatnio:prawie 5 lat
  • Postów:177
4
Juhas napisał(a):

Porównałeś klasę nie będącą thread safe z singletonem będącym therad safe. Gdzie tu sens? Poza tym przy pobieraniu danych z bazy potrzebuję tylko jednego połączenia z bazą danych. A więc jednej instancji klasy. Zgadza się? Do takich rzeczy przecież jest stworzony singleton.

Chodzi o implementację. Po co bawić się w pisanie zbędnego kodu, skoro singleton ma być jedyną instancją klasy, to po prostu robisz jedną instancję. Dla mnie właśnie głupie jest pisanie zbędnego kodu.

A co do połączenia z bazą - wszystko zależy od architektury projektu. Możesz przekazywać sobie tylko connection stringa, albo fabrykę połączeń. Nie bardzo widzę sytuację, w której przekazujesz sobie np. obiekt SqlConnection... no ale ja mało w życiu widziałem.

edytowany 1x, ostatnio: john_klamka
S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
1

Singletona to możesz używasz jak masz klasę z jakimś configiem może, ale w innych przypadkach nie wystarczy po prostu użyć raz new?


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0
Juhas napisał(a):

Porównałeś klasę nie będącą thread safe z singletonem będącym therad safe. Gdzie tu sens? Poza tym przy pobieraniu danych z bazy potrzebuję tylko jednego połączenia z bazą danych. A więc jednej instancji klasy. Zgadza się? Do takich rzeczy przecież jest stworzony singleton.

A jak Ty masz zamiar zarządzać tymi połączeniami i transakcjami? Jedno połączenie otwarte przez cały czas życia aplikacji? A transakcje będziesz otwierał i zamykał w metodach tego singletona?

S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
0

@somekind: a jak Ty to robisz z ciekawości? Bo u mnie jest tak że korzystam z zarządzania transakcjami przez Spring i programowanie aspektowe :)


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
2

@scibi92: no ja nie mam Springa, więc po prostu rejestruję w kontenerze SessionFactory jako singletona, a Session per lifetime scope. Do tego mam zarejestrowany interceptor, który opakowuje wskazane (atrybutem) metody/klasy, i podczas ich wykonywania otwiera transakcje, wykonuje kod metody, potem zatwierdza transakcję (albo wycofuje jeśli poleciał wyjątek). Wewnątrz metody operuję po prostu na obiekcie session (Get/Load/Save/Delete/QueryOver), nie mam żadnych commitów, rollbacków, ani nawet try-catch.

grzesiek51114
grzesiek51114
więc po prostu rejestruję w kontenerze SessionFactory jako singletona, a Session per lifetime scope: zuch! Robię tak samo :P EDIT: Kurcze... w sumie nie wiem po co to napisałem...
somekind
Bądź hipsterem, zrób odwrotnie. :P
S9
  • Rejestracja:ponad 10 lat
  • Ostatnio:5 miesięcy
  • Lokalizacja:Warszawa
  • Postów:3573
0

Ale ja w praktyce tez tak robie. Mam TransactionManagera w Springu z odpalonym trybem adnotacji i dzięki temu nad klasami/metodami piszę

Kopiuj
@Transactional

Spring dzięki programowaniu aspektowemu sam zarządza transakcjami tylko mu trzeba niewiele zadeklarować. Po prostu byłem ciekawy jak to wygląda w C# i tych frameworkach


"w haśle <młody dynamiczny zespół> nie chodzi o to ile masz lat tylko jak często zmienia się skład"
edytowany 1x, ostatnio: scibi92
JU
  • Rejestracja:około 22 lata
  • Ostatnio:około miesiąc
  • Postów:5042
0
somekind napisał(a):

A jak Ty masz zamiar zarządzać tymi połączeniami i transakcjami? Jedno połączenie otwarte przez cały czas życia aplikacji? A transakcje będziesz otwierał i zamykał w metodach tego singletona?

Tak. Aplikacja jest bazodanowa i ciągle korzysta z bazy danych. Łączenie się z bazą po to, żeby wykonać jedno zapytanie i potem się rozłączyć jest bez sensu. Zresztą na czymś takim przejechałem się laaata temu.

YA
  • Rejestracja:prawie 10 lat
  • Ostatnio:około godziny
  • Postów:2367
2
Juhas napisał(a):

@somekind: nie chcę tworzyć profilaktycznie dla wszystkich klas :| Chcę stworzyć dla tej jednej mapującej dane z bazy danych.
@john_klamka: co za różnica, czy stworzę sobie singletona, czy obiekt w entry poincie, który będę przekazywał dalej? Dla mnie to drugie wyjście jest głupie. Czemu miałoby być lepsze?

Różnica jest taka, że w przypadku drugim (przekazywanie utworzonego obiektu) masz kontrolę nad tym, gdzie jest przekazywany. W przypadku singletona, mało ogarnięty programista, może stanąć przed pokusą wywołana singletona w warstwie, w której nie powinien się ów singleton znaleźć, np. w widoku wywoła singletona, który z bazy coś tam wyciągnie i złamie zasadę separacji odpowiedzialności na poziomie komponentów (np. widok wyciągający dane z bazy).

Z mojej perspektywy sprawa prosta, albo zdajesz sobie sprawę z decyzji projektowych i ich wpływu na późniejsze zmiany, albo nie. Według mnie przed pójściem w singletony trzeba zadać sobie sporo pytań... przykładowo:
a) jaki problem ten singleton rozwiązuje?
b) skąd wziął się problem?
c) czy mam alternatywę?
d) czy z singletonem będzie skalowalne?
e) czy z singletonem będzie thread safe?
f) czy separacja classloaderów nie sprawi, że może pojawić się więcej instancji singletona :)
g) jak singleton będzie zachowywał się w klastrze
e) czy będzie dało się go "zamockować"
...

somekind
f) czy podczas odstrzeliwania stopy przestrzelić sobie tez kolano? ;)
YA
f) to dla hardkorów, którzy wyjechali poza c :D
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0
scibi92 napisał(a):

Spring dzięki programowaniu aspektowemu sam zarządza transakcjami tylko mu trzeba niewiele zadeklarować.

Jakbym chciał, to pewno znalazłbym jakiś framework dotnetowy, który dałby mi to wszystko. Tylko akurat coś takiego wolę dopasować do projektu. A "programowaniem aspektowym" zapewne też nazywasz proxy nad Twoją klasą utworzone przez kontener IoC.

Po prostu byłem ciekawy jak to wygląda w C# i tych frameworkach

Tzn. ja opisałem swój (pragmatyczny) przypadek. Ale wielu programistów otwiera sobie sesję ORMa w każdej oddzielnej metodzie, w każdej mają try-catch, i zatwierdzanie zmian... No cóż, niektórym widocznie płacą za linijkę. ;)

Juhas napisał(a):

Tak. Aplikacja jest bazodanowa i ciągle korzysta z bazy danych. Łączenie się z bazą po to, żeby wykonać jedno zapytanie i potem się rozłączyć jest bez sensu. Zresztą na czymś takim przejechałem się laaata temu.

Przejechałeś się pewnie dlatego, że implementowałeś ręcznie... tak jak chcesz zrobić teraz.

W normalnej sytuacji, połączenie jest pobierane z puli na potrzeby pojedynczej transakcji, po jej zakończeniu wraca do puli, aby inny kod mógł go użyć. A fizycznymi połączeniami zajmuje się pooler, nie programista.

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:27 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
1

To nie robicie jak ludzie, ze jest po prostu jedna, dwie metody utilsowe, które jako parametr przyjmują funkcję do wykonania na DB (z argumentem w postaci sesji)?
Tak jak tu:
https://www.jooq.org/doc/3.8/manual/sql-execution/transaction-management/

Nic nie trzeba rejestrować.
Żadnych aspektów/ procy cudów.
Testowanie proste.


jeden i pół terabajta powinno wystarczyć każdemu
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)